/*!
    \file generator_python.cpp
    \brief Fast binary encoding Python generator implementation
    \author Ivan Shynkarenka
    \date 24.04.2018
    \copyright MIT License
*/

#include "generator_python.h"

namespace FBE {

void GeneratorPython::Generate(const std::shared_ptr<Package>& package)
{
    GeneratePackage(package);
}

void GeneratorPython::GenerateHeader(const std::string& source)
{
    std::string code = R"CODE(#------------------------------------------------------------------------------
# Automatically generated by the Fast Binary Encoding compiler, do not modify!
# https://github.com/chronoxor/FastBinaryEncoding
# Source: _INPUT_
# FBE version: _VERSION_
#------------------------------------------------------------------------------
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFooter()
{

}

void GeneratorPython::GenerateInit(const CppCommon::Path& path)
{
    // Open the init file
    CppCommon::Path init = path / "__init__.py";
    WriteBegin();

    // Generate init header
    GenerateHeader("FBE");

    // Generate init footer
    GenerateFooter();

    // Close the init file
    WriteEnd();
    Store(init);
}

void GeneratorPython::GenerateFBE(const CppCommon::Path& path)
{
    // Generate the common file
    CppCommon::Path common = path / "fbe.py";
    WriteBegin();

    // Generate common header
    GenerateHeader("FBE");

    std::string code = R"CODE(
import base64
import decimal
import enum
import json
import struct
import sys
import time
import uuid


class DefaultEnumMeta(enum.EnumMeta):
    default = object()

    def __call__(cls, value=default, *args, **kwargs):
        if value is DefaultEnumMeta.default:
            return next(iter(cls))
        return super().__call__(value, *args, **kwargs)


class JSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "__to_json__"):
            return obj.__to_json__()
        elif isinstance(obj, bytes) or isinstance(obj, bytearray):
            return base64.b64encode(obj).decode('ascii')
        elif isinstance(obj, decimal.Decimal):
            return str(obj)
        elif isinstance(obj, uuid.UUID):
            return str(obj)
        elif isinstance(obj, set):
            return list(obj)
        else:
            return super().default(self, obj)


def epoch():
    return 0


def utc():
    return time.time_ns()
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate common models
    GenerateFBEWriteBuffer();
    GenerateFBEReadBuffer();
    GenerateFBEModel();
    GenerateFBEFieldModelBase();
    GenerateFBEFieldModel();
    GenerateFBEFieldModel("Bool", "bool", "1", "False");
    GenerateFBEFieldModel("Byte", "byte", "1", "0");
    GenerateFBEFieldModel("Char", "char", "1", "'\\0'");
    GenerateFBEFieldModel("WChar", "wchar", "4", "'\\0'");
    GenerateFBEFieldModel("Int8", "int8", "1", "0");
    GenerateFBEFieldModel("UInt8", "uint8", "1", "0");
    GenerateFBEFieldModel("Int16", "int16", "2", "0");
    GenerateFBEFieldModel("UInt16", "uint16", "2", "0");
    GenerateFBEFieldModel("Int32", "int32", "4", "0");
    GenerateFBEFieldModel("UInt32", "uint32", "4", "0");
    GenerateFBEFieldModel("Int64", "int64", "8", "0");
    GenerateFBEFieldModel("UInt64", "uint64", "8", "0");
    GenerateFBEFieldModel("Float", "float", "4", "0.0");
    GenerateFBEFieldModel("Double", "double", "8", "0.0");
    GenerateFBEFieldModel("Timestamp", "uint64", "8", "0");
    GenerateFBEFieldModel("UUID", "uuid", "16", "uuid.UUID(int=0)");
    GenerateFBEFieldModelDecimal();
    GenerateFBEFieldModelBytes();
    GenerateFBEFieldModelString();
    GenerateFBEFieldModelOptional();
    GenerateFBEFieldModelArray();
    GenerateFBEFieldModelVector();
    GenerateFBEFieldModelSet();
    GenerateFBEFieldModelMap();
    if (Final())
    {
        GenerateFBEFinalModel();
        GenerateFBEFinalModel("Bool", "bool", "1", "False");
        GenerateFBEFinalModel("Byte", "byte", "1", "0");
        GenerateFBEFinalModel("Char", "char", "1", "'\\0'");
        GenerateFBEFinalModel("WChar", "wchar", "4", "'\\0'");
        GenerateFBEFinalModel("Int8", "int8", "1", "0");
        GenerateFBEFinalModel("UInt8", "uint8", "1", "0");
        GenerateFBEFinalModel("Int16", "int16", "2", "0");
        GenerateFBEFinalModel("UInt16", "uint16", "2", "0");
        GenerateFBEFinalModel("Int32", "int32", "4", "0");
        GenerateFBEFinalModel("UInt32", "uint32", "4", "0");
        GenerateFBEFinalModel("Int64", "int64", "8", "0");
        GenerateFBEFinalModel("UInt64", "uint64", "8", "0");
        GenerateFBEFinalModel("Float", "float", "4", "0.0");
        GenerateFBEFinalModel("Double", "double", "8", "0.0");
        GenerateFBEFinalModel("Timestamp", "uint64", "8", "0");
        GenerateFBEFinalModel("UUID", "uuid", "16", "uuid.UUID(int=0)");
        GenerateFBEFinalModelDecimal();
        GenerateFBEFinalModelBytes();
        GenerateFBEFinalModelString();
        GenerateFBEFinalModelOptional();
        GenerateFBEFinalModelArray();
        GenerateFBEFinalModelVector();
        GenerateFBEFinalModelSet();
        GenerateFBEFinalModelMap();
    }
    if (Proto())
    {
        GenerateFBESender();
        GenerateFBEReceiver();
    }

    // Generate common footer
    GenerateFooter();

    // Close the common file
    WriteEnd();
    Store(common);
}

void GeneratorPython::GenerateFBEWriteBuffer()
{
    std::string code = R"CODE(

# Fast Binary Encoding write buffer based on the dynamic byte array
class WriteBuffer(object):
    __slots__ = "_buffer", "_offset", "_size",

    def __init__(self, capacity=0):
        self._buffer = bytearray(capacity)
        self._size = 0
        self._offset = 0

    def __bool__(self):
        return not self.empty

    @property
    def empty(self):
        return (self._buffer is None) or (self._size <= 0)

    @property
    def buffer(self):
        return self._buffer

    @property
    def capacity(self):
        return len(self._buffer)

    @property
    def size(self):
        return self._size

    def __len__(self):
        return self._size

    @property
    def offset(self):
        return self._offset

    # Attach an empty memory buffer
    def attach_new(self):
        self._buffer = bytearray()
        self._size = 0
        self._offset = 0

    # Attach an empty memory buffer with a given capacity
    def attach_capacity(self, capacity):
        self._buffer = bytearray(capacity)
        self._size = 0
        self._offset = 0

    # Attach a given memory buffer
    def attach_buffer(self, buffer, offset=0, size=None):
        assert (buffer is not None), "Invalid buffer!"
        if buffer is None:
            raise ValueError("Invalid buffer!")

        if size is None:
            size = len(buffer)

        assert (size > 0), "Invalid size!"
        if size <= 0:
            raise ValueError("Invalid size!")
        assert (offset <= size), "Invalid offset!"
        if offset > size:
            raise ValueError("Invalid offset!")

        if isinstance(buffer, ReadBuffer) or isinstance(buffer, WriteBuffer):
            self._buffer = buffer.buffer
        else:
            self._buffer = buffer
        self._size = size
        self._offset = offset

    # Allocate memory in the current write buffer and return offset to the allocated memory block
    def allocate(self, size):
        assert (size >= 0), "Invalid allocation size!"
        if size < 0:
            raise ValueError("Invalid allocation size!")

        offset = self._size

        # Calculate a new buffer size
        total = self._size + size

        if total <= len(self._buffer):
            self._size = total
            return offset

        data = bytearray(max(total, 2 * len(self._buffer)))
        data[:self._size] = self._buffer
        self._buffer = data
        self._size = total
        return offset

    # Remove some memory of the given size from the current write buffer
    def remove(self, offset, size):
        assert ((offset + size) <= len(self._buffer)), "Invalid offset & size!"
        if (offset + size) > len(self._buffer):
            raise ValueError("Invalid offset & size!")

        del self._buffer[offset:offset + size]
        self._size -= size
        if self._offset >= (offset + size):
            self._offset -= size
        elif self._offset >= offset:
            self._offset -= self._offset - offset
            if self._offset > self._size:
                self._offset = self._size

    # Reserve memory of the given capacity in the current write buffer
    def reserve(self, capacity):
        assert (capacity >= 0), "Invalid reserve capacity!"
        if capacity < 0:
            raise ValueError("Invalid reserve capacity!")

        if capacity > len(self._buffer):
            data = bytearray(max(capacity, 2 * len(self._buffer)))
            data[:self._size] = self._buffer
            self._buffer = data

    # Resize the current write buffer
    def resize(self, size):
        self.reserve(size)
        self._size = size
        if self._offset > self._size:
            self._offset = self._size

    # Reset the current write buffer and its offset
    def reset(self):
        self._size = 0
        self._offset = 0

    # Shift the current write buffer offset
    def shift(self, offset):
        self._offset += offset

    # Unshift the current write buffer offset
    def unshift(self, offset):
        self._offset -= offset
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEReadBuffer()
{
    std::string code = R"CODE(

# Fast Binary Encoding read buffer based on the constant byte buffer
class ReadBuffer(object):
    __slots__ = "_buffer", "_offset", "_size",

    def __init__(self):
        self._buffer = None
        self._size = 0
        self._offset = 0

    def __bool__(self):
        return self._buffer is not None

    @property
    def buffer(self):
        return self._buffer

    @property
    def capacity(self):
        return self._size

    @property
    def size(self):
        return self._size

    def __len__(self):
        return self._size

    @property
    def offset(self):
        return self._offset

    # Attach a given memory buffer
    def attach_buffer(self, buffer, offset=0, size=None):
        assert (buffer is not None), "Invalid buffer!"
        if buffer is None:
            raise ValueError("Invalid buffer!")

        if size is None:
            size = len(buffer)

        assert (size > 0), "Invalid size!"
        if size <= 0:
            raise ValueError("Invalid size!")
        assert (offset <= size), "Invalid offset!"
        if offset > size:
            raise ValueError("Invalid offset!")

        if isinstance(buffer, ReadBuffer) or isinstance(buffer, WriteBuffer):
            self._buffer = buffer.buffer
        else:
            self._buffer = buffer
        self._size = size
        self._offset = offset

    # Reset the current read buffer and its offset
    def reset(self):
        self._buffer = None
        self._size = 0
        self._offset = 0

    # Shift the current read buffer offset
    def shift(self, offset):
        self._offset += offset

    # Unshift the current read buffer offset
    def unshift(self, offset):
        self._offset -= offset
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEModel()
{
    std::string code = R"CODE(

# Fast Binary Encoding base model
class Model(object):
    __slots__ = "_buffer",

    def __init__(self, buffer=None):
        if buffer is None:
            buffer = WriteBuffer()
        self._buffer = buffer

    @property
    def buffer(self):
        return self._buffer

    # Attach an empty memory buffer
    def attach_new(self):
        self.buffer.attach_new()

    # Attach an empty memory buffer with a given capacity
    def attach_capacity(self, capacity):
        self.buffer.attach_capacity(capacity)

    # Attach a given memory buffer
    def attach_buffer(self, buffer, offset=0, size=None):
        self.buffer.attach_buffer(buffer, offset, size)

    # Allocate memory in the current write buffer and return offset to the allocated memory block
    def allocate(self, size):
        return self.buffer.allocate(size)

    # Remove some memory of the given size from the current write buffer
    def remove(self, offset, size):
        self.buffer.remove(offset, size)

    # Reserve memory of the given capacity in the current write buffer
    def reserve(self, capacity):
        self.buffer.reserve(capacity)

    # Resize the current write buffer
    def resize(self, size):
        self.buffer.resize(size)

    # Reset the current write buffer and its offset
    def reset(self):
        self.buffer.reset()

    # Shift the current write buffer offset
    def shift(self, offset):
        self.buffer.shift(offset)

    # Unshift the current write buffer offset
    def unshift(self, offset):
        self.buffer.unshift(offset)

    # Buffer I/O methods

    def read_uint32(self, offset):
        return struct.unpack_from("<I", self.buffer.buffer, self.buffer.offset + offset)[0]

    def write_uint32(self, offset, value):
        return struct.pack_into("<I", self.buffer.buffer, self.buffer.offset + offset, value)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelBase()
{
    std::string code = R"CODE(

# Fast Binary Encoding base field model
class FieldModelBase(object):
    __slots__ = "_buffer", "_offset",

    def __init__(self, buffer, offset):
        self._buffer = buffer
        self._offset = offset

    # Get the field offset
    @property
    def fbe_offset(self):
        return self._offset

    # Set the field offset
    @fbe_offset.setter
    def fbe_offset(self, offset):
        self._offset = offset

    # Get the field size
    @property
    def fbe_size(self):
        return 0

    # Get the field extra size
    @property
    def fbe_extra(self):
        return 0

    # Shift the current field offset
    def fbe_shift(self, offset):
        self._offset += offset

    # Unshift the current field offset
    def fbe_unshift(self, offset):
        self._offset -= offset

    # Buffer I/O methods

    def read_bool(self, offset):
        return struct.unpack_from("<?", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_byte(self, offset):
        return struct.unpack_from("<B", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_char(self, offset):
        return chr(struct.unpack_from("<B", self._buffer.buffer, self._buffer.offset + offset)[0])

    def read_wchar(self, offset):
        return chr(struct.unpack_from("<I", self._buffer.buffer, self._buffer.offset + offset)[0])

    def read_int8(self, offset):
        return struct.unpack_from("<b", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_uint8(self, offset):
        return struct.unpack_from("<B", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_int16(self, offset):
        return struct.unpack_from("<h", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_uint16(self, offset):
        return struct.unpack_from("<H", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_int32(self, offset):
        return struct.unpack_from("<i", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_uint32(self, offset):
        return struct.unpack_from("<I", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_int64(self, offset):
        return struct.unpack_from("<q", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_uint64(self, offset):
        return struct.unpack_from("<Q", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_uint64_be(self, offset):
        return struct.unpack_from(">Q", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_float(self, offset):
        return struct.unpack_from("<f", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_double(self, offset):
        return struct.unpack_from("<d", self._buffer.buffer, self._buffer.offset + offset)[0]

    def read_bytes(self, offset, size):
        return self._buffer.buffer[self._buffer.offset + offset:self._buffer.offset + offset + size]

    def read_uuid(self, offset):
        return uuid.UUID(int=((self.read_uint64_be(offset) << 64) | self.read_uint64_be(offset + 8)))

    def write_bool(self, offset, value):
        struct.pack_into("<?", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_byte(self, offset, value):
        struct.pack_into("<B", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_char(self, offset, value):
        struct.pack_into("<B", self._buffer.buffer, self._buffer.offset + offset, ord(value))

    def write_wchar(self, offset, value):
        struct.pack_into("<I", self._buffer.buffer, self._buffer.offset + offset, ord(value))

    def write_int8(self, offset, value):
        struct.pack_into("<b", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_uint8(self, offset, value):
        struct.pack_into("<B", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_int16(self, offset, value):
        struct.pack_into("<h", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_uint16(self, offset, value):
        struct.pack_into("<H", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_int32(self, offset, value):
        struct.pack_into("<i", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_uint32(self, offset, value):
        struct.pack_into("<I", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_int64(self, offset, value):
        struct.pack_into("<q", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_uint64(self, offset, value):
        struct.pack_into("<Q", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_float(self, offset, value):
        struct.pack_into("<f", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_double(self, offset, value):
        struct.pack_into("<d", self._buffer.buffer, self._buffer.offset + offset, value)

    def write_bytes(self, offset, buffer):
        self._buffer.buffer[self._buffer.offset + offset:self._buffer.offset + offset + len(buffer)] = buffer

    def write_count(self, offset, value, value_count):
        for i in range(value_count):
            self._buffer.buffer[self._buffer.offset + offset + i] = value

    def write_uuid(self, offset, value):
        self._buffer.buffer[self._buffer.offset + offset:self._buffer.offset + offset + 16] = value.bytes
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModel()
{
    std::string code = R"CODE(

# Fast Binary Encoding field model
class FieldModel(FieldModelBase):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Check if the value is valid
    def verify(self):
        return True
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModel(const std::string& name, const std::string& type, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(

# Fast Binary Encoding _TYPE_ field model
class FieldModel_NAME_(FieldModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the field size
    @property
    def fbe_size(self):
        return _SIZE_

    # Get the value
    def get(self, defaults=None):
        if defaults is None:
            defaults = _DEFAULTS_

        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return defaults

        return self.read__TYPE_(self.fbe_offset)

    # Set the value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        self.write__TYPE_(self.fbe_offset, value)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelDecimal()
{
    std::string code = R"CODE(

# Fast Binary Encoding decimal field model
class FieldModelDecimal(FieldModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the field size
    @property
    def fbe_size(self):
        return 16

    # Get the decimal value
    def get(self, defaults=None):
        if defaults is None:
            defaults = decimal.Decimal(0)

        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return defaults

        # Read decimal parts
        low = self.read_uint32(self.fbe_offset)
        mid = self.read_uint32(self.fbe_offset + 4)
        high = self.read_uint32(self.fbe_offset + 8)
        flags = self.read_uint32(self.fbe_offset + 12)

        # Calculate decimal value
        negative = (flags & 0x80000000) != 0
        scale = (flags & 0x7FFFFFFF) >> 16
        result = decimal.Decimal(high) * 18446744073709551616
        result += decimal.Decimal(mid) * 4294967296
        result += decimal.Decimal(low)
        result /= pow(10, scale)
        if negative:
            result = -result

        return result

    # Set the decimal value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        # Extract decimal parts
        parts = value.as_tuple()
        negative = True if parts.sign == 1 else False
        number = int(''.join(map(str, parts.digits)))
        scale = -parts.exponent

        # Check for decimal number overflow
        bits = number.bit_length()
        if (bits < 0) or (bits > 96):
            # Value too big for .NET Decimal (bit length is limited to [0, 96])
            self.write_count(self.fbe_offset, 0, self.fbe_size)
            return

        # Check for decimal scale overflow
        if (scale < 0) or (scale > 28):
            # Value scale exceeds .NET Decimal limit of [0, 28]
            self.write_count(self.fbe_offset, 0, self.fbe_size)
            return

        # Write unscaled value to bytes 0-11
        self.write_bytes(self.fbe_offset, number.to_bytes(12, "little"))

        # Write scale at byte 14
        self.write_byte(self.fbe_offset + 14, scale)

        # Write signum at byte 15
        self.write_byte(self.fbe_offset + 15, 0x80 if negative else 0)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelBytes()
{
    std::string code = R"CODE(

# Fast Binary Encoding bytes field model
class FieldModelBytes(FieldModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the field size
    @property
    def fbe_size(self):
        return 4

    # Get the field extra size
    @property
    def fbe_extra(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_bytes_offset = self.read_uint32(self.fbe_offset)
        if (fbe_bytes_offset == 0) or ((self._buffer.offset + fbe_bytes_offset + 4) > self._buffer.size):
            return 0

        fbe_bytes_size = self.read_uint32(fbe_bytes_offset)
        return 4 + fbe_bytes_size

    # Check if the bytes value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return True

        fbe_bytes_offset = self.read_uint32(self.fbe_offset)
        if fbe_bytes_offset == 0:
            return True

        if (self._buffer.offset + fbe_bytes_offset + 4) > self._buffer.size:
            return False

        fbe_bytes_size = self.read_uint32(fbe_bytes_offset)
        if (self._buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) > self._buffer.size:
            return False

        return True

    # Get the bytes value
    def get(self, defaults=None):
        if defaults is None:
            defaults = bytearray()

        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return defaults

        fbe_bytes_offset = self.read_uint32(self.fbe_offset)
        if fbe_bytes_offset == 0:
            return defaults

        assert ((self._buffer.offset + fbe_bytes_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + fbe_bytes_offset + 4) > self._buffer.size:
            return defaults

        fbe_bytes_size = self.read_uint32(fbe_bytes_offset)
        assert ((self._buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) > self._buffer.size:
            return defaults

        return self.read_bytes(fbe_bytes_offset + 4, fbe_bytes_size)

    # Set the bytes value
    def set(self, value):
        assert (value is not None), "Invalid bytes value!"
        if value is None:
            raise ValueError("Invalid bytes value!")

        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        fbe_bytes_size = len(value)
        fbe_bytes_offset = self._buffer.allocate(4 + fbe_bytes_size) - self._buffer.offset
        assert ((fbe_bytes_offset > 0) and ((self._buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) <= self._buffer.size)), "Model is broken!"
        if (fbe_bytes_offset <= 0) or ((self._buffer.offset + fbe_bytes_offset + 4 + fbe_bytes_size) > self._buffer.size):
            return

        self.write_uint32(self.fbe_offset, fbe_bytes_offset)
        self.write_uint32(fbe_bytes_offset, fbe_bytes_size)
        self.write_bytes(fbe_bytes_offset + 4, value)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelString()
{
    std::string code = R"CODE(

# Fast Binary Encoding string field model
class FieldModelString(FieldModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the field size
    @property
    def fbe_size(self):
        return 4

    # Get the field extra size
    @property
    def fbe_extra(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_string_offset = self.read_uint32(self.fbe_offset)
        if (fbe_string_offset == 0) or ((self._buffer.offset + fbe_string_offset + 4) > self._buffer.size):
            return 0

        fbe_string_size = self.read_uint32(fbe_string_offset)
        return 4 + fbe_string_size

    # Check if the string value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return True

        fbe_string_offset = self.read_uint32(self.fbe_offset)
        if fbe_string_offset == 0:
            return True

        if (self._buffer.offset + fbe_string_offset + 4) > self._buffer.size:
            return False

        fbe_string_size = self.read_uint32(fbe_string_offset)
        if (self._buffer.offset + fbe_string_offset + 4 + fbe_string_size) > self._buffer.size:
            return False

        return True

    # Get the string value
    def get(self, defaults=None):
        if defaults is None:
            defaults = ""

        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return defaults

        fbe_string_offset = self.read_uint32(self.fbe_offset)
        if fbe_string_offset == 0:
            return defaults

        assert ((self._buffer.offset + fbe_string_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + fbe_string_offset + 4) > self._buffer.size:
            return defaults

        fbe_string_size = self.read_uint32(fbe_string_offset)
        assert ((self._buffer.offset + fbe_string_offset + 4 + fbe_string_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + fbe_string_offset + 4 + fbe_string_size) > self._buffer.size:
            return defaults

        data = self.read_bytes(fbe_string_offset + 4, fbe_string_size)
        return data.decode("utf-8")

    # Set the string value
    def set(self, value):
        assert (value is not None), "Invalid string value!"
        if value is None:
            raise ValueError("Invalid string value!")

        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        data = value.encode("utf-8")

        fbe_string_size = len(data)
        fbe_string_offset = self._buffer.allocate(4 + fbe_string_size) - self._buffer.offset
        assert ((fbe_string_offset > 0) and ((self._buffer.offset + fbe_string_offset + 4 + fbe_string_size) <= self._buffer.size)), "Model is broken!"
        if (fbe_string_offset <= 0) or ((self._buffer.offset + fbe_string_offset + 4 + fbe_string_size) > self._buffer.size):
            return

        self.write_uint32(self.fbe_offset, fbe_string_offset)
        self.write_uint32(fbe_string_offset, fbe_string_size)
        self.write_bytes(fbe_string_offset + 4, data)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelOptional()
{
    std::string code = R"CODE(

# Fast Binary Encoding optional field model
class FieldModelOptional(FieldModel):
    __slots__ = "_model",

    def __init__(self, model, buffer, offset):
        super().__init__(buffer, offset)
        self._model = model
        self._model.fbe_offset = 0

    # Get the field size
    @property
    def fbe_size(self):
        return 1 + 4

    # Get the field extra size
    @property
    def fbe_extra(self):
        if not self.has_value:
            return 0

        fbe_optional_offset = self.read_uint32(self.fbe_offset + 1)
        if (fbe_optional_offset == 0) or ((self._buffer.offset + fbe_optional_offset + 4) > self._buffer.size):
            return 0

        self._buffer.shift(fbe_optional_offset)
        fbe_result = self.value.fbe_size + self.value.fbe_extra
        self._buffer.unshift(fbe_optional_offset)
        return fbe_result

    # Checks if the object contains a value
    def __bool__(self):
        return self.has_value()

    # Checks if the object contains a value
    @property
    def has_value(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return False

        fbe_has_value = self.read_uint8(self.fbe_offset)
        return fbe_has_value != 0

    # Get the base field model value
    @property
    def value(self):
        return self._model

    # Check if the optional value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return True

        fbe_has_value = self.read_uint8(self.fbe_offset)
        if fbe_has_value == 0:
            return True

        fbe_optional_offset = self.read_uint32(self.fbe_offset + 1)
        if fbe_optional_offset == 0:
            return False

        self._buffer.shift(fbe_optional_offset)
        fbe_result = self.value.verify()
        self._buffer.unshift(fbe_optional_offset)
        return fbe_result

    # Get the optional value (being phase)
    def get_begin(self):
        if not self.has_value:
            return 0

        fbe_optional_offset = self.read_uint32(self.fbe_offset + 1)
        assert (fbe_optional_offset > 0), "Model is broken!"
        if fbe_optional_offset <= 0:
            return 0

        self._buffer.shift(fbe_optional_offset)
        return fbe_optional_offset

    # Get the optional value (end phase)
    def get_end(self, fbe_begin):
        self._buffer.unshift(fbe_begin)

    # Get the optional value
    def get(self, defaults=None):
        fbe_begin = self.get_begin()
        if fbe_begin == 0:
            return defaults
        optional = self.value.get()
        self.get_end(fbe_begin)
        return optional

    # Set the optional value (begin phase)
    def set_begin(self, has_value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_has_value = 1 if has_value else 0
        self.write_uint8(self.fbe_offset, fbe_has_value)
        if fbe_has_value == 0:
            return 0

        fbe_optional_size = self.value.fbe_size
        fbe_optional_offset = self._buffer.allocate(fbe_optional_size) - self._buffer.offset
        assert ((fbe_optional_offset > 0) and ((self._buffer.offset + fbe_optional_offset + fbe_optional_size) <= self._buffer.size)), "Model is broken!"
        if (fbe_optional_offset <= 0) or ((self._buffer.offset + fbe_optional_offset + fbe_optional_size) > self._buffer.size):
            return 0

        self.write_uint32(self.fbe_offset + 1, fbe_optional_offset)

        self._buffer.shift(fbe_optional_offset)
        return fbe_optional_offset

    # Set the optional value (end phase)
    def set_end(self, fbe_begin):
        self._buffer.unshift(fbe_begin)

    # Set the optional value
    def set(self, optional):
        fbe_begin = self.set_begin(optional is not None)
        if fbe_begin == 0:
            return
        self.value.set(optional)
        self.set_end(fbe_begin)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelArray()
{
    std::string code = R"CODE(

# Fast Binary Encoding array field model
class FieldModelArray(FieldModel):
    __slots__ = "_model", "_size",

    def __init__(self, model, buffer, offset, size):
        super().__init__(buffer, offset)
        self._model = model
        self._size = size

    # Get the field size
    @property
    def fbe_size(self):
        return self._size * self._model.fbe_size

    # Get the field extra size
    @property
    def fbe_extra(self):
        return 0

    # Get the array offset
    @property
    def offset(self):
        return 0

    # Get the array size
    @property
    def size(self):
        return self._size

    # Array index operator
    def __getitem__(self, index):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        assert (index < self._size), "Index is out of bounds!"
        if index >= self._size:
            raise IndexError("Index is out of bounds!")

        self._model.fbe_offset = self.fbe_offset
        self._model.fbe_shift(index * self._model.fbe_size)
        return self._model

    # Check if the array is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return False

        self._model.fbe_offset = self.fbe_offset
        for i in range(self._size):
            if not self._model.verify():
                return False
            self._model.fbe_shift(self._model.fbe_size)

        return True

    # Get the array
    def get(self, values=None):
        if values is None:
            values = list()

        values.clear()

        fbe_model = self[0]
        for i in range(self._size):
            value = fbe_model.get()
            values.append(value)
            fbe_model.fbe_shift(fbe_model.fbe_size)

        return values

    # Set the array
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        fbe_model = self[0]
        for i in range(min(len(values), self._size)):
            fbe_model.set(values[i])
            fbe_model.fbe_shift(fbe_model.fbe_size)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelVector()
{
    std::string code = R"CODE(

# Fast Binary Encoding vector field model
class FieldModelVector(FieldModel):
    __slots__ = "_model",

    def __init__(self, model, buffer, offset):
        super().__init__(buffer, offset)
        self._model = model

    # Get the field size
    @property
    def fbe_size(self):
        return 4

    # Get the field extra size
    @property
    def fbe_extra(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_vector_offset = self.read_uint32(self.fbe_offset)
        if (fbe_vector_offset == 0) or ((self._buffer.offset + fbe_vector_offset + 4) > self._buffer.size):
            return 0

        fbe_vector_size = self.read_uint32(fbe_vector_offset)

        fbe_result = 4
        self._model.fbe_offset = fbe_vector_offset + 4
        for i in range(fbe_vector_size):
            fbe_result += self._model.fbe_size + self._model.fbe_extra
            self._model.fbe_shift(self._model.fbe_size)
        return fbe_result

    # Get the vector offset
    @property
    def offset(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_vector_offset = self.read_uint32(self.fbe_offset)
        return fbe_vector_offset

    # Get the vector size
    @property
    def size(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_vector_offset = self.read_uint32(self.fbe_offset)
        if (fbe_vector_offset == 0) or ((self._buffer.offset + fbe_vector_offset + 4) > self._buffer.size):
            return 0

        fbe_vector_size = self.read_uint32(fbe_vector_offset)
        return fbe_vector_size

    # Vector index operator
    def __getitem__(self, index):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"

        fbe_vector_offset = self.read_uint32(self.fbe_offset)
        assert ((fbe_vector_offset > 0) and ((self._buffer.offset + fbe_vector_offset + 4) <= self._buffer.size)), "Model is broken!"

        fbe_vector_size = self.read_uint32(fbe_vector_offset)
        assert (index < fbe_vector_size), "Index is out of bounds!"
        if index >= fbe_vector_size:
            raise IndexError("Index is out of bounds!")

        self._model.fbe_offset = fbe_vector_offset + 4
        self._model.fbe_shift(index * self._model.fbe_size)
        return self._model

    # Resize the vector and get its first model
    def resize(self, size):
        fbe_vector_size = size * self._model.fbe_size
        fbe_vector_offset = self._buffer.allocate(4 + fbe_vector_size) - self._buffer.offset
        assert ((fbe_vector_offset > 0) and ((self._buffer.offset + fbe_vector_offset + 4) <= self._buffer.size)), "Model is broken!"

        self.write_uint32(self.fbe_offset, fbe_vector_offset)
        self.write_uint32(fbe_vector_offset, size)
        self.write_count(fbe_vector_offset + 4, 0, fbe_vector_size)

        self._model.fbe_offset = fbe_vector_offset + 4
        return self._model

    # Check if the vector is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return True

        fbe_vector_offset = self.read_uint32(self.fbe_offset)
        if fbe_vector_offset == 0:
            return True

        if (self._buffer.offset + fbe_vector_offset + 4) > self._buffer.size:
            return False

        fbe_vector_size = self.read_uint32(fbe_vector_offset)

        self._model.fbe_offset = fbe_vector_offset + 4
        for i in range(fbe_vector_size):
            if not self._model.verify():
                return False
            self._model.fbe_shift(self._model.fbe_size)

        return True

    # Get the vector
    def get(self, values=None):
        if values is None:
            values = list()

        values.clear()

        fbe_vector_size = self.size
        if fbe_vector_size == 0:
            return values

        fbe_model = self[0]
        for i in range(fbe_vector_size):
            value = fbe_model.get()
            values.append(value)
            fbe_model.fbe_shift(fbe_model.fbe_size)

        return values

    # Set the vector
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        fbe_model = self.resize(len(values))
        for value in values:
            fbe_model.set(value)
            fbe_model.fbe_shift(fbe_model.fbe_size)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelSet()
{
    std::string code = R"CODE(

# Fast Binary Encoding set field model
class FieldModelSet(FieldModel):
    __slots__ = "_model",

    def __init__(self, model, buffer, offset):
        super().__init__(buffer, offset)
        self._model = model

    # Get the field size
    @property
    def fbe_size(self):
        return 4

    # Get the field extra size
    @property
    def fbe_extra(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_set_offset = self.read_uint32(self.fbe_offset)
        if (fbe_set_offset == 0) or ((self._buffer.offset + fbe_set_offset + 4) > self._buffer.size):
            return 0

        fbe_set_size = self.read_uint32(fbe_set_offset)

        fbe_result = 4
        self._model.fbe_offset = fbe_set_offset + 4
        for i in range(fbe_set_size):
            fbe_result += self._model.fbe_size + self._model.fbe_extra
            self._model.fbe_shift(self._model.fbe_size)
        return fbe_result

    # Get the set value offset
    @property
    def offset(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_set_offset = self.read_uint32(self.fbe_offset)
        return fbe_set_offset

    # Get the set value size
    @property
    def size(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_set_offset = self.read_uint32(self.fbe_offset)
        if (fbe_set_offset == 0) or ((self._buffer.offset + fbe_set_offset + 4) > self._buffer.size):
            return 0

        fbe_set_size = self.read_uint32(fbe_set_offset)
        return fbe_set_size

    # Set index operator
    def __getitem__(self, index):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"

        fbe_set_offset = self.read_uint32(self.fbe_offset)
        assert ((fbe_set_offset > 0) and ((self._buffer.offset + fbe_set_offset + 4) <= self._buffer.size)), "Model is broken!"

        fbe_set_size = self.read_uint32(fbe_set_offset)
        assert (index < fbe_set_size), "Index is out of bounds!"
        if index >= fbe_set_size:
            raise IndexError("Index is out of bounds!")

        self._model.fbe_offset = fbe_set_offset + 4
        self._model.fbe_shift(index * self._model.fbe_size)
        return self._model

    # Resize the set and get its first model
    def resize(self, size):
        fbe_set_size = size * self._model.fbe_size
        fbe_set_offset = self._buffer.allocate(4 + fbe_set_size) - self._buffer.offset
        assert ((fbe_set_offset > 0) and ((self._buffer.offset + fbe_set_offset + 4) <= self._buffer.size)), "Model is broken!"

        self.write_uint32(self.fbe_offset, fbe_set_offset)
        self.write_uint32(fbe_set_offset, size)
        self.write_count(fbe_set_offset + 4, 0, fbe_set_size)

        self._model.fbe_offset = fbe_set_offset + 4
        return self._model

    # Check if the set value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return True

        fbe_set_offset = self.read_uint32(self.fbe_offset)
        if fbe_set_offset == 0:
            return True

        if (self._buffer.offset + fbe_set_offset + 4) > self._buffer.size:
            return False

        fbe_set_size = self.read_uint32(fbe_set_offset)

        self._model.fbe_offset = fbe_set_offset + 4
        for i in range(fbe_set_size):
            if not self._model.verify():
                return False
            self._model.fbe_shift(self._model.fbe_size)

        return True

    # Get the set value
    def get(self, values=None):
        if values is None:
            values = set()

        values.clear()

        fbe_set_size = self.size
        if fbe_set_size == 0:
            return values

        fbe_model = self[0]
        for i in range(fbe_set_size):
            value = fbe_model.get()
            values.add(value)
            fbe_model.fbe_shift(fbe_model.fbe_size)

        return values

    # Set the set value
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        fbe_model = self.resize(len(values))
        for value in values:
            fbe_model.set(value)
            fbe_model.fbe_shift(fbe_model.fbe_size)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFieldModelMap()
{
    std::string code = R"CODE(

# Fast Binary Encoding map field model
class FieldModelMap(FieldModel):
    __slots__ = "_model_key", "_model_value",

    def __init__(self, model_key, model_value, buffer, offset):
        super().__init__(buffer, offset)
        self._model_key = model_key
        self._model_value = model_value

    # Get the field size
    @property
    def fbe_size(self):
        return 4

    # Get the field extra size
    @property
    def fbe_extra(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_map_offset = self.read_uint32(self.fbe_offset)
        if (fbe_map_offset == 0) or ((self._buffer.offset + fbe_map_offset + 4) > self._buffer.size):
            return 0

        fbe_map_size = self.read_uint32(fbe_map_offset)

        fbe_result = 4
        self._model_key.fbe_offset = fbe_map_offset + 4
        self._model_value.fbe_offset = fbe_map_offset + 4 + self._model_key.fbe_size
        for i in range(fbe_map_size):
            fbe_result += self._model_key.fbe_size + self._model_key.fbe_extra
            self._model_key.fbe_shift(self._model_key.fbe_size + self._model_value.fbe_size)
            fbe_result += self._model_value.fbe_size + self._model_value.fbe_extra
            self._model_value.fbe_shift(self._model_key.fbe_size + self._model_value.fbe_size)
        return fbe_result

    # Get the map offset
    @property
    def offset(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_map_offset = self.read_uint32(self.fbe_offset)
        return fbe_map_offset

    # Get the map size
    @property
    def size(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        fbe_map_offset = self.read_uint32(self.fbe_offset)
        if (fbe_map_offset == 0) or ((self._buffer.offset + fbe_map_offset + 4) > self._buffer.size):
            return 0

        fbe_map_size = self.read_uint32(fbe_map_offset)
        return fbe_map_size

    # Map index operator
    def __getitem__(self, index):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"

        fbe_map_offset = self.read_uint32(self.fbe_offset)
        assert ((fbe_map_offset > 0) and ((self._buffer.offset + fbe_map_offset + 4) <= self._buffer.size)), "Model is broken!"

        fbe_map_size = self.read_uint32(fbe_map_offset)
        assert (index < fbe_map_size), "Index is out of bounds!"
        if index >= fbe_map_size:
            raise IndexError("Index is out of bounds!")

        self._model_key.fbe_offset = fbe_map_offset + 4
        self._model_value.fbe_offset = fbe_map_offset + 4 + self._model_key.fbe_size
        self._model_key.fbe_shift(index * (self._model_key.fbe_size + self._model_value.fbe_size))
        self._model_value.fbe_shift(index * (self._model_key.fbe_size + self._model_value.fbe_size))
        return self._model_key, self._model_value

    # Resize the map and get its first model
    def resize(self, size):
        fbe_map_size = size * (self._model_key.fbe_size + self._model_value.fbe_size)
        fbe_map_offset = self._buffer.allocate(4 + fbe_map_size) - self._buffer.offset
        assert ((fbe_map_offset > 0) and ((self._buffer.offset + fbe_map_offset + 4) <= self._buffer.size)), "Model is broken!"

        self.write_uint32(self.fbe_offset, fbe_map_offset)
        self.write_uint32(fbe_map_offset, size)
        self.write_count(fbe_map_offset + 4, 0, fbe_map_size)

        self._model_key.fbe_offset = fbe_map_offset + 4
        self._model_value.fbe_offset = fbe_map_offset + 4 + self._model_key.fbe_size
        return self._model_key, self._model_value

    # Check if the map is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return True

        fbe_map_offset = self.read_uint32(self.fbe_offset)
        if fbe_map_offset == 0:
            return True

        if (self._buffer.offset + fbe_map_offset + 4) > self._buffer.size:
            return False

        fbe_map_size = self.read_uint32(fbe_map_offset)

        self._model_key.fbe_offset = fbe_map_offset + 4
        self._model_value.fbe_offset = fbe_map_offset + 4 + self._model_key.fbe_size
        for i in range(fbe_map_size):
            if not self._model_key.verify():
                return False
            self._model_key.fbe_shift(self._model_key.fbe_size + self._model_value.fbe_size)
            if not self._model_value.verify():
                return False
            self._model_value.fbe_shift(self._model_key.fbe_size + self._model_value.fbe_size)

        return True

    # Get the map
    def get(self, values=None):
        if values is None:
            values = dict()

        values.clear()

        fbe_map_size = self.size
        if fbe_map_size == 0:
            return values

        (fbe_model_key, fbe_model_value) = self[0]
        for i in range(fbe_map_size):
            key = fbe_model_key.get()
            value = fbe_model_value.get()
            values[key] = value
            fbe_model_key.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)
            fbe_model_value.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)

        return values

    # Set the map
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        (fbe_model_key, fbe_model_value) = self.resize(len(values))
        for (key, value) in values.items():
            fbe_model_key.set(key)
            fbe_model_key.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)
            fbe_model_value.set(value)
            fbe_model_value.fbe_shift(fbe_model_key.fbe_size + fbe_model_value.fbe_size)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModel()
{
    std::string code = R"CODE(

# Fast Binary Encoding final model
class FinalModel(FieldModelBase):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Check if the value is valid
    def verify(self):
        raise NotImplementedError("verify() method not implemented!")
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModel(const std::string& name, const std::string& type, const std::string& size, const std::string& defaults)
{
    std::string code = R"CODE(

# Fast Binary Encoding _TYPE_ final model
class FinalModel_NAME_(FinalModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the allocation size
    # noinspection PyUnusedLocal
    def fbe_allocation_size(self, value):
        return self.fbe_size

    # Get the final size
    @property
    def fbe_size(self):
        return _SIZE_

    # Check if the value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return sys.maxsize

        return self.fbe_size

    # Get the value
    def get(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return _DEFAULTS_, 0

        return self.read__TYPE_(self.fbe_offset), self.fbe_size

    # Set the value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        self.write__TYPE_(self.fbe_offset, value)
        return self.fbe_size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelDecimal()
{
    std::string code = R"CODE(

# Fast Binary Encoding decimal final model
class FinalModelDecimal(FieldModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the allocation size
    # noinspection PyUnusedLocal
    def fbe_allocation_size(self, value):
        return self.fbe_size

    # Get the final size
    @property
    def fbe_size(self):
        return 16

    # Check if the decimal value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return sys.maxsize

        return self.fbe_size

    # Get the decimal value
    def get(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return decimal.Decimal(0), 0

        # Read decimal parts
        low = self.read_uint32(self.fbe_offset)
        mid = self.read_uint32(self.fbe_offset + 4)
        high = self.read_uint32(self.fbe_offset + 8)
        flags = self.read_uint32(self.fbe_offset + 12)

        # Calculate decimal value
        negative = (flags & 0x80000000) != 0
        scale = (flags & 0x7FFFFFFF) >> 16
        result = decimal.Decimal(high) * 18446744073709551616
        result += decimal.Decimal(mid) * 4294967296
        result += decimal.Decimal(low)
        result /= pow(10, scale)
        if negative:
            result = -result

        return result, self.fbe_size

    # Set the decimal value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        # Extract decimal parts
        parts = value.as_tuple()
        negative = True if parts.sign == 1 else False
        number = int(''.join(map(str, parts.digits)))
        scale = -parts.exponent

        # Check for decimal number overflow
        if (number.bit_length() < 0) or (number.bit_length() > 96):
            # Value too big for .NET Decimal (bit length is limited to [0, 96])
            self.write_count(self.fbe_offset, 0, self.fbe_size)
            return self.fbe_size

        # Check for decimal scale overflow
        if (scale < 0) or (scale > 28):
            # Value scale exceeds .NET Decimal limit of [0, 28]
            self.write_count(self.fbe_offset, 0, self.fbe_size)
            return self.fbe_size

        # Write unscaled value to bytes 0-11
        self.write_bytes(self.fbe_offset, number.to_bytes(12, "little"))

        # Write scale at byte 14
        self.write_byte(self.fbe_offset + 14, scale)

        # Write signum at byte 15
        self.write_byte(self.fbe_offset + 15, 0x80 if negative else 0)
        return self.fbe_size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelBytes()
{
    std::string code = R"CODE(

# Fast Binary Encoding bytes final model
class FinalModelBytes(FinalModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the allocation size
    # noinspection PyMethodMayBeStatic
    def fbe_allocation_size(self, value):
        return 4 + len(value)

    # Check if the bytes value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return sys.maxsize

        fbe_bytes_size = self.read_uint32(self.fbe_offset)
        if (self._buffer.offset + self.fbe_offset + 4 + fbe_bytes_size) > self._buffer.size:
            return sys.maxsize

        return 4 + fbe_bytes_size

    # Get the bytes value
    def get(self):
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return bytearray(), 0

        fbe_bytes_size = self.read_uint32(self.fbe_offset)
        assert ((self._buffer.offset + self.fbe_offset + 4 + fbe_bytes_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4 + fbe_bytes_size) > self._buffer.size:
            return bytearray(), 4

        return self.read_bytes(self.fbe_offset + 4, fbe_bytes_size), (4 + fbe_bytes_size)

    # Set the bytes value
    def set(self, value):
        assert (value is not None), "Invalid bytes value!"
        if value is None:
            raise ValueError("Invalid bytes value!")

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return 0

        fbe_bytes_size = len(value)
        assert ((self._buffer.offset + self.fbe_offset + 4 + fbe_bytes_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4 + fbe_bytes_size) > self._buffer.size:
            return 4

        self.write_uint32(self.fbe_offset, fbe_bytes_size)
        self.write_bytes(self.fbe_offset + 4, value)
        return 4 + fbe_bytes_size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelString()
{
    std::string code = R"CODE(

# Fast Binary Encoding string final model
class FinalModelString(FinalModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the allocation size
    # noinspection PyMethodMayBeStatic
    def fbe_allocation_size(self, value):
        return 4 + 3 * (len(value) + 1)

    # Check if the string value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return sys.maxsize

        fbe_string_size = self.read_uint32(self.fbe_offset)
        if (self._buffer.offset + self.fbe_offset + 4 + fbe_string_size) > self._buffer.size:
            return sys.maxsize

        return 4 + fbe_string_size

    # Get the string value
    def get(self):
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return '', 0

        fbe_string_size = self.read_uint32(self.fbe_offset)
        assert ((self._buffer.offset + self.fbe_offset + 4 + fbe_string_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4 + fbe_string_size) > self._buffer.size:
            return '', 4

        data = self.read_bytes(self.fbe_offset + 4, fbe_string_size)
        return data.decode("utf-8"), (4 + fbe_string_size)

    # Set the string value
    def set(self, value):
        assert (value is not None), "Invalid string value!"
        if value is None:
            raise ValueError("Invalid string value!")

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return 0

        data = value.encode("utf-8")

        fbe_string_size = len(data)
        assert ((self._buffer.offset + self.fbe_offset + 4 + fbe_string_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4 + fbe_string_size) > self._buffer.size:
            return 4

        self.write_uint32(self.fbe_offset, fbe_string_size)
        self.write_bytes(self.fbe_offset + 4, data)
        return 4 + fbe_string_size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelOptional()
{
    std::string code = R"CODE(

# Fast Binary Encoding optional final model
class FinalModelOptional(FinalModel):
    __slots__ = "_model",

    def __init__(self, model, buffer, offset):
        super().__init__(buffer, offset)
        self._model = model
        self._model.fbe_offset = 0

    # Get the allocation size
    # noinspection PyMethodMayBeStatic
    def fbe_allocation_size(self, optional):
        return 1 + (self.value.fbe_allocation_size(optional) if optional else 0)

    # Checks if the object contains a value
    def __bool__(self):
        return self.has_value()

    # Checks if the object contains a value
    @property
    def has_value(self):
        if (self._buffer.offset + self.fbe_offset + 1) > self._buffer.size:
            return False

        fbe_has_value = self.read_uint8(self.fbe_offset)
        return fbe_has_value != 0

    # Get the base final model value
    @property
    def value(self):
        return self._model

    # Check if the optional value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + 1) > self._buffer.size:
            return sys.maxsize

        fbe_has_value = self.read_uint8(self.fbe_offset)
        if fbe_has_value == 0:
            return 1

        self._buffer.shift(self.fbe_offset + 1)
        fbe_result = self.value.verify()
        self._buffer.unshift(self.fbe_offset + 1)
        return 1 + fbe_result

    # Get the optional value
    def get(self):
        if (self._buffer.offset + self.fbe_offset + 1) > self._buffer.size:
            return None, 0

        if not self.has_value:
            return None, 1

        self._buffer.shift(self.fbe_offset + 1)
        optional = self.value.get()
        self._buffer.unshift(self.fbe_offset + 1)
        return optional[0], (1 + optional[1])

    # Set the optional value
    def set(self, optional):
        assert ((self._buffer.offset + self.fbe_offset + 1) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 1) > self._buffer.size:
            return 0

        fbe_has_value = 1 if optional else 0
        self.write_uint8(self.fbe_offset, fbe_has_value)
        if fbe_has_value == 0:
            return 1

        self._buffer.shift(self.fbe_offset + 1)
        size = self.value.set(optional)
        self._buffer.unshift(self.fbe_offset + 1)
        return 1 + size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelArray()
{
    std::string code = R"CODE(

# Fast Binary Encoding array final model
class FinalModelArray(FinalModel):
    __slots__ = "_model", "_size",

    def __init__(self, model, buffer, offset, size):
        super().__init__(buffer, offset)
        self._model = model
        self._size = size

    # Get the allocation size
    # noinspection PyMethodMayBeStatic
    def fbe_allocation_size(self, values):
        size = 0
        for i in range(min(len(values), self._size)):
            size += self._model.fbe_allocation_size(values[i])
        return size

    # Check if the array is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset) > self._buffer.size:
            return sys.maxsize

        size = 0
        self._model.fbe_offset = self.fbe_offset
        for i in range(self._size):
            offset = self._model.verify()
            if offset == sys.maxsize:
                return sys.maxsize
            self._model.fbe_shift(offset)
            size += offset
        return size

    # Get the array
    def get(self, values=None):
        if values is None:
            values = list()

        values.clear()

        assert ((self._buffer.offset + self.fbe_offset) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset) > self._buffer.size:
            return values, 0

        size = 0
        self._model.fbe_offset = self.fbe_offset
        for i in range(self._size):
            value = self._model.get()
            values.append(value[0])
            self._model.fbe_shift(value[1])
            size += value[1]
        return values, size

    # Set the array
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset) > self._buffer.size:
            return 0

        size = 0
        self._model.fbe_offset = self.fbe_offset
        for i in range(min(len(values), self._size)):
            offset = self._model.set(values[i])
            self._model.fbe_shift(offset)
            size += offset
        return size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelVector()
{
    std::string code = R"CODE(

# Fast Binary Encoding vector final model
class FinalModelVector(FinalModel):
    __slots__ = "_model",

    def __init__(self, model, buffer, offset):
        super().__init__(buffer, offset)
        self._model = model

    # Get the allocation size
    # noinspection PyMethodMayBeStatic
    def fbe_allocation_size(self, values):
        size = 4
        for value in values:
            size += self._model.fbe_allocation_size(value)
        return size

    # Check if the vector is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return sys.maxsize

        fbe_vector_size = self.read_uint32(self.fbe_offset)

        size = 4
        self._model.fbe_offset = self.fbe_offset + 4
        for i in range(fbe_vector_size):
            offset = self._model.verify()
            if offset == sys.maxsize:
                return sys.maxsize
            self._model.fbe_shift(offset)
            size += offset
        return size

    # Get the vector
    def get(self, values=None):
        if values is None:
            values = list()

        values.clear()

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return values, 0

        fbe_vector_size = self.read_uint32(self.fbe_offset)
        if fbe_vector_size == 0:
            return values, 4

        size = 4
        self._model.fbe_offset = self.fbe_offset + 4
        for i in range(fbe_vector_size):
            value = self._model.get()
            values.append(value[0])
            self._model.fbe_shift(value[1])
            size += value[1]
        return values, size

    # Set the vector
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return 0

        self.write_uint32(self.fbe_offset, len(values))

        size = 4
        self._model.fbe_offset = self.fbe_offset + 4
        for value in values:
            offset = self._model.set(value)
            self._model.fbe_shift(offset)
            size += offset
        return size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelSet()
{
    std::string code = R"CODE(

# Fast Binary Encoding set final model
class FinalModelSet(FinalModel):
    __slots__ = "_model",

    def __init__(self, model, buffer, offset):
        super().__init__(buffer, offset)
        self._model = model

    # Get the allocation size
    # noinspection PyMethodMayBeStatic
    def fbe_allocation_size(self, values):
        size = 4
        for value in values:
            size += self._model.fbe_allocation_size(value)
        return size

    # Check if the set value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return sys.maxsize

        fbe_set_size = self.read_uint32(self.fbe_offset)

        size = 4
        self._model.fbe_offset = self.fbe_offset + 4
        for i in range(fbe_set_size):
            offset = self._model.verify()
            if offset == sys.maxsize:
                return sys.maxsize
            self._model.fbe_shift(offset)
            size += offset
        return size

    # Get the set value
    def get(self, values=None):
        if values is None:
            values = set()

        values.clear()

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return values, 0

        fbe_set_size = self.read_uint32(self.fbe_offset)
        if fbe_set_size == 0:
            return values, 4

        size = 4
        self._model.fbe_offset = self.fbe_offset + 4
        for i in range(fbe_set_size):
            value = self._model.get()
            values.add(value[0])
            self._model.fbe_shift(value[1])
            size += value[1]
        return values, size

    # Set the set value
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return 0

        self.write_uint32(self.fbe_offset, len(values))

        size = 4
        self._model.fbe_offset = self.fbe_offset + 4
        for value in values:
            offset = self._model.set(value)
            self._model.fbe_shift(offset)
            size += offset
        return size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEFinalModelMap()
{
    std::string code = R"CODE(

# Fast Binary Encoding map final model
class FinalModelMap(FinalModel):
    __slots__ = "_model_key", "_model_value",

    def __init__(self, model_key, model_value, buffer, offset):
        super().__init__(buffer, offset)
        self._model_key = model_key
        self._model_value = model_value

    # Get the allocation size
    # noinspection PyMethodMayBeStatic
    def fbe_allocation_size(self, values):
        size = 4
        for (key, value) in values.items():
            size += self._model_key.fbe_allocation_size(key)
            size += self._model_value.fbe_allocation_size(value)
        return size

    # Check if the map is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return sys.maxsize

        fbe_map_size = self.read_uint32(self.fbe_offset)

        size = 4
        self._model_key.fbe_offset = self.fbe_offset + 4
        self._model_value.fbe_offset = self.fbe_offset + 4
        for i in range(fbe_map_size):
            offset_key = self._model_key.verify()
            if offset_key == sys.maxsize:
                return sys.maxsize
            self._model_key.fbe_shift(offset_key)
            self._model_value.fbe_shift(offset_key)
            size += offset_key
            offset_value = self._model_value.verify()
            if offset_value == sys.maxsize:
                return sys.maxsize
            self._model_key.fbe_shift(offset_value)
            self._model_value.fbe_shift(offset_value)
            size += offset_value
        return size

    # Get the map
    def get(self, values=None):
        if values is None:
            values = dict()

        values.clear()

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return values, 0

        fbe_map_size = self.read_uint32(self.fbe_offset)
        if fbe_map_size == 0:
            return values, 4

        size = 4
        self._model_key.fbe_offset = self.fbe_offset + 4
        self._model_value.fbe_offset = self.fbe_offset + 4
        for i in range(fbe_map_size):
            key = self._model_key.get()
            self._model_key.fbe_shift(key[1])
            self._model_value.fbe_shift(key[1])
            size += key[1]
            value = self._model_value.get()
            self._model_key.fbe_shift(value[1])
            self._model_value.fbe_shift(value[1])
            size += value[1]
            values[key[0]] = value[0]
        return values, size

    # Set the map
    def set(self, values):
        assert (values is not None), "Invalid values parameter!"
        if values is None:
            raise ValueError("Invalid values parameter!")

        assert ((self._buffer.offset + self.fbe_offset + 4) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + 4) > self._buffer.size:
            return 0

        self.write_uint32(self.fbe_offset, len(values))

        size = 4
        self._model_key.fbe_offset = self.fbe_offset + 4
        self._model_value.fbe_offset = self.fbe_offset + 4
        for (key, value) in values.items():
            offset_key = self._model_key.set(key)
            self._model_key.fbe_shift(offset_key)
            self._model_value.fbe_shift(offset_key)
            size += offset_key
            offset_value = self._model_value.set(value)
            self._model_key.fbe_shift(offset_value)
            self._model_value.fbe_shift(offset_value)
            size += offset_value
        return size
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBESender()
{
    std::string code = R"CODE(

# Fast Binary Encoding base sender
class Sender(object):
    __slots__ = "_buffer", "_logging", "_final",

    def __init__(self, buffer=None, final=False):
        if buffer is None:
            buffer = WriteBuffer()
        self._buffer = buffer
        self._logging = False
        self._final = final

    # Get the bytes buffer
    @property
    def buffer(self):
        return self._buffer

    # Get the final protocol flag
    @property
    def final(self):
        return self._final

    # Get the logging flag
    @property
    def logging(self):
        return self._logging

    # Set the logging flag
    @logging.setter
    def logging(self, logging):
        self._logging = logging

    # Reset the sender buffer
    def reset(self):
        self._buffer.reset()

    # Send serialized buffer.
    # Direct call of the method requires knowledge about internals of FBE models serialization.
    # Use it with care!
    def send_serialized(self, serialized):
        assert (serialized > 0), "Invalid size of the serialized buffer!"
        if serialized <= 0:
            return 0

        # Shift the send buffer
        self._buffer.shift(serialized)

        # Send the value
        sent = self.on_send(self._buffer.buffer, 0, self._buffer.size)
        self._buffer.remove(0, sent)
        return sent

    # Send message handler
    def on_send(self, buffer, offset, size):
        raise NotImplementedError("Abstract method call!")

    # Send log message handler
    def on_send_log(self, message):
        pass
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFBEReceiver()
{
    std::string code = R"CODE(

# Fast Binary Encoding base receiver
class Receiver(object):
    __slots__ = "_buffer", "_logging", "_final",

    def __init__(self, buffer=None, final=False):
        if buffer is None:
            buffer = WriteBuffer()
        self._buffer = buffer
        self._logging = False
        self._final = final

    # Get the bytes buffer
    @property
    def buffer(self):
        return self._buffer

    # Get the final protocol flag
    @property
    def final(self):
        return self._final

    # Get the logging flag
    @property
    def logging(self):
        return self._logging

    # Set the logging flag
    @logging.setter
    def logging(self, logging):
        self._logging = logging

    # Reset the receiver buffer
    def reset(self):
        self._buffer.reset()

    # Receive data
    def receive(self, buffer, offset=0, size=None):
        assert (buffer is not None), "Invalid buffer!"
        if buffer is None:
            raise ValueError("Invalid buffer!")

        if size is None:
            size = len(buffer)

        assert ((offset + size) <= len(buffer)), "Invalid offset & size!"
        if (offset + size) > len(buffer):
            raise ValueError("Invalid offset & size!")

        if size == 0:
            return

        if isinstance(buffer, ReadBuffer) or isinstance(buffer, WriteBuffer):
            buffer = buffer.buffer

        # Storage buffer
        offset0 = self._buffer.offset
        offset1 = self._buffer.size
        size1 = self._buffer.size

        # Receive buffer
        offset2 = 0
        size2 = size

        # While receive buffer is available to handle...
        while offset2 < size2:
            message_buffer = None
            message_offset = 0
            message_size = 0

            # Try to receive message size
            message_size_copied = False
            message_size_found = False
            while not message_size_found:
                # Look into the storage buffer
                if offset0 < size1:
                    count = min(size1 - offset0, 4)
                    if count == 4:
                        message_size_copied = True
                        message_size_found = True
                        message_size = Receiver.read_uint32(self._buffer.buffer, self._buffer.offset + offset0)
                        offset0 += 4
                        break
                    else:
                        # Fill remaining data from the receive buffer
                        if offset2 < size2:
                            count = min(size2 - offset2, 4 - count)

                            # Allocate and refresh the storage buffer
                            self._buffer.allocate(count)
                            size1 += count

                            self._buffer.buffer[offset1:offset1 + count] = buffer[offset + offset2:offset + offset2 + count]
                            offset1 += count
                            offset2 += count
                            continue
                        else:
                            break

                # Look into the receive buffer
                if offset2 < size2:
                    count = min(size2 - offset2, 4)
                    if count == 4:
                        message_size_found = True
                        message_size = Receiver.read_uint32(buffer, offset + offset2)
                        offset2 += 4
                        break
                    else:
                        # Allocate and refresh the storage buffer
                        self._buffer.allocate(count)
                        size1 += count

                        self._buffer.buffer[offset1:offset1 + count] = buffer[offset + offset2:offset + offset2 + count]
                        offset1 += count
                        offset2 += count
                        continue
                else:
                    break

            if not message_size_found:
                return

            # Check the message full size
            min_size = (4 + 4) if self._final else (4 + 4 + 4 + 4)
            assert (message_size >= min_size), "Invalid receive data!"
            if message_size < min_size:
                return

            # Try to receive message body
            message_found = False
            while not message_found:
                # Look into the storage buffer
                if offset0 < size1:
                    count = min(size1 - offset0, message_size - 4)
                    if count == (message_size - 4):
                        message_found = True
                        message_buffer = self._buffer.buffer
                        message_offset = offset0 - 4
                        offset0 += message_size - 4
                        break
                    else:
                        # Fill remaining data from the receive buffer
                        if offset2 < size2:
                            # Copy message size into the storage buffer
                            if not message_size_copied:
                                # Allocate and refresh the storage buffer
                                self._buffer.allocate(4)
                                size1 += 4

                                Receiver.write_uint32(self._buffer.buffer, self._buffer.offset + offset0, message_size)
                                offset0 += 4
                                offset1 += 4

                                message_size_copied = True

                            count = min(size2 - offset2, message_size - 4 - count)

                            # Allocate and refresh the storage buffer
                            self._buffer.allocate(count)
                            size1 += count

                            self._buffer.buffer[offset1:offset1 + count] = buffer[offset + offset2:offset + offset2 + count]
                            offset1 += count
                            offset2 += count
                            continue
                        else:
                            break

                # Look into the receive buffer
                if offset2 < size2:
                    count = min(size2 - offset2, message_size - 4)
                    if not message_size_copied and (count == (message_size - 4)):
                        message_found = True
                        message_buffer = buffer
                        message_offset = offset + offset2 - 4
                        offset2 += message_size - 4
                        break
                    else:
                        # Copy message size into the storage buffer
                        if not message_size_copied:
                            # Allocate and refresh the storage buffer
                            self._buffer.allocate(4)
                            size1 += 4

                            Receiver.write_uint32(self._buffer.buffer, self._buffer.offset + offset0, message_size)
                            offset0 += 4
                            offset1 += 4

                            message_size_copied = True

                        # Allocate and refresh the storage buffer
                        self._buffer.allocate(count)
                        size1 += count

                        self._buffer.buffer[offset1:offset1 + count] = buffer[offset + offset2:offset + offset2 + count]
                        offset1 += count
                        offset2 += count
                        continue
                else:
                    break

            if not message_found:
                # Copy message size into the storage buffer
                if not message_size_copied:
                    # Allocate and refresh the storage buffer
                    self._buffer.allocate(4)
                    size1 += 4

                    Receiver.write_uint32(self._buffer.buffer, self._buffer.offset + offset0, message_size)
                    offset0 += 4
                    offset1 += 4

                    # noinspection PyUnusedLocal
                    message_size_copied = True
                return

            # Read the message parameters
            if self._final:
                # noinspection PyUnusedLocal
                fbe_struct_size = Receiver.read_uint32(message_buffer, message_offset)
                fbe_struct_type = Receiver.read_uint32(message_buffer, message_offset + 4)
            else:
                fbe_struct_offset = Receiver.read_uint32(message_buffer, message_offset + 4)
                # noinspection PyUnusedLocal
                fbe_struct_size = Receiver.read_uint32(message_buffer, message_offset + fbe_struct_offset)
                fbe_struct_type = Receiver.read_uint32(message_buffer, message_offset + fbe_struct_offset + 4)

            # Handle the message
            self.on_receive(fbe_struct_type, message_buffer, message_offset, message_size)

            # Reset the storage buffer
            self._buffer.reset()

            # Refresh the storage buffer
            offset0 = self._buffer.offset
            offset1 = self._buffer.size
            size1 = self._buffer.size

    # Receive message handler
    def on_receive(self, type, buffer, offset, size):
        raise NotImplementedError("Abstract method call!")

    # Receive log message handler
    def on_receive_log(self, message):
        pass

    # Buffer I/O methods

    @staticmethod
    def read_uint32(buffer, offset):
        return struct.unpack_from("<I", buffer, offset)[0]

    @staticmethod
    def write_uint32(buffer, offset, value):
        return struct.pack_into("<I", buffer, offset, value)
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateImports(const std::shared_ptr<Package>& p)
{
    // Generate common import
    WriteLine();
    if (JSON())
        WriteLineIndent("import base64");
    WriteLineIndent("import decimal");
    WriteLineIndent("import enum");
    WriteLineIndent("import functools");
    if (JSON())
        WriteLineIndent("import json");
    WriteLineIndent("import sys");
    WriteLineIndent("import uuid");

    // Generate FBE import
    WriteLine();
    WriteLineIndent("import fbe");

    // Generate packages import
    if (p->import)
        for (const auto& import : p->import->imports)
            WriteLineIndent("from . import " + *import);
}

void GeneratorPython::GeneratePackage(const std::shared_ptr<Package>& p)
{
    CppCommon::Path output = _output;

    // Create package path
    CppCommon::Directory::CreateTree(output);

    // Generate __init__ file
    GenerateInit(output);

    // Generate FBE file
    GenerateFBE(output);

    // Generate the output file
    output /= *p->name + ".py";
    WriteBegin();

    // Generate package header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    // Generate namespace
    if (p->body)
    {
        // Generate child enums
        for (const auto& child_e : p->body->enums)
            GenerateEnum(child_e);

        // Generate child flags
        for (const auto& child_f : p->body->flags)
            GenerateFlags(child_f);

        // Generate child structs
        for (const auto& child_s : p->body->structs)
            GenerateStruct(child_s);
    }

    // Generate protocol
    if (Proto())
    {
        // Generate protocol version
        GenerateProtocolVersion(p);

        // Generate sender & receiver
        GenerateSender(p, false);
        GenerateReceiver(p, false);
        GenerateProxy(p, false);
        if (Final())
        {
            GenerateSender(p, true);
            GenerateReceiver(p, true);
        }
    }

    // Generate package footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorPython::GenerateEnum(const std::shared_ptr<EnumType>& e)
{
    // Generate enum begin
    WriteLine();
    WriteLine();
    WriteLineIndent("class " + *e->name + "(enum.IntEnum, metaclass=fbe.DefaultEnumMeta):");
    Indent(1);

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Generate enum body
    if (e->body)
    {
        int index = 0;
        std::string last = ConvertEnumConstant("int32", "0");
        for (const auto& value : e->body->values)
        {
            WriteIndent(*value->name + " = ");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(enum_type, *value->value->constant);
                    Write(last + " + " + std::to_string(index++));
                }
                else if (value->value->reference && !value->value->reference->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant("", *value->value->reference);
                    Write(last);
                }
            }
            else
                Write(last + " + " + std::to_string(index++));
            WriteLine();
        }
        WriteLineIndent("unknown = ~0");
    }

    // Generate enum __slots__
    WriteLine();
    WriteLineIndent("__slots__ = ()");

    // Generate enum __format__ method
    WriteLine();
    WriteLineIndent("def __format__(self, format_spec):");
    Indent(1);
    WriteLineIndent("return self.__str__()");
    Indent(-1);

    // Generate enum __str__ method
    WriteLine();
    WriteLineIndent("def __str__(self):");
    Indent(1);
    if (e->body && !e->body->values.empty())
    {
        for (auto it = e->body->values.begin(); it != e->body->values.end(); ++it)
        {
            WriteLineIndent("if self.value == " + *e->name + "." + *(*it)->name + ":");
            Indent(1);
            WriteLineIndent("return \"" + *(*it)->name + "\"");
            Indent(-1);
        }
        WriteLineIndent("return \"<unknown>\"");
    }
    else
        WriteLineIndent("return \"<empty>\"");
    Indent(-1);

    // Generate enum __format__ method
    WriteLine();
    WriteLineIndent("@classmethod");
    WriteLineIndent("def _missing_(cls, value):");
    Indent(1);
    WriteLineIndent("return " + *e->name + ".unknown");
    Indent(-1);

    if (JSON())
    {
        // Generate enum __from_json__ method
        WriteLine();
        WriteLineIndent("@staticmethod");
        WriteLineIndent("def __from_json__(value):");
        Indent(1);
        WriteLineIndent("if value is None:");
        Indent(1);
        WriteLineIndent("return None");
        Indent(-1);
        WriteLineIndent("return " + *e->name + "(value)");
        Indent(-1);
    }

    // Generate enum end
    Indent(-1);

    // Generate enum field model
    GenerateEnumFieldModel(e);

    // Generate enum final model
    if (Final())
        GenerateEnumFinalModel(e);
}

void GeneratorPython::GenerateEnumFieldModel(const std::shared_ptr<EnumType>& e)
{
    std::string code = R"CODE(

# Fast Binary Encoding _ENUM_NAME_ field model
class FieldModel_ENUM_NAME_(fbe.FieldModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the field size
    @property
    def fbe_size(self):
        return _ENUM_SIZE_

    # Get the value
    def get(self, defaults=None):
        if defaults is None:
            defaults = _ENUM_NAME_()

        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return defaults

        return _ENUM_NAME_(self.read__ENUM_TYPE_(self.fbe_offset))

    # Set the value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        self.write__ENUM_TYPE_(self.fbe_offset, value)
)CODE";

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), *e->name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), ConvertEnumType(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateEnumFinalModel(const std::shared_ptr<EnumType>& e)
{
    std::string code = R"CODE(

# Fast Binary Encoding _ENUM_NAME_ final model
class FinalModel_ENUM_NAME_(fbe.FinalModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the allocation size
    # noinspection PyUnusedLocal
    def fbe_allocation_size(self, value):
        return self.fbe_size

    # Get the final size
    @property
    def fbe_size(self):
        return _ENUM_SIZE_

    # Check if the value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return sys.maxsize

        return self.fbe_size

    # Get the value
    def get(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return _ENUM_NAME_(), 0

        return _ENUM_NAME_(self.read__ENUM_TYPE_(self.fbe_offset)), self.fbe_size

    # Set the value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        self.write__ENUM_TYPE_(self.fbe_offset, value)
        return self.fbe_size
)CODE";

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_ENUM_NAME_"), *e->name);
    code = std::regex_replace(code, std::regex("_ENUM_TYPE_"), ConvertEnumType(enum_type));
    code = std::regex_replace(code, std::regex("_ENUM_SIZE_"), ConvertEnumSize(enum_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFlags(const std::shared_ptr<FlagsType>& f)
{
    // Generate flags begin
    WriteLine();
    WriteLine();
    WriteLineIndent("class " + *f->name + "(enum.IntFlag, metaclass=fbe.DefaultEnumMeta):");
    Indent(1);

    std::string enum_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Generate flags body
    if (f->body && !f->body->values.empty())
    {
        for (const auto& value : f->body->values)
        {
            WriteIndent(*value->name + " = ");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                    Write(ConvertEnumConstant(enum_type, *value->value->constant));
                else if (value->value->reference && !value->value->reference->empty())
                    Write(ConvertEnumConstant("", *value->value->reference));
            }
            WriteLine();
        }
        WriteLine();
    }

    // Generate flags __slots__
    WriteLineIndent("__slots__ = ()");

    // Generate flags has_flags() method
    WriteLine();
    WriteLineIndent("# Is flags set?");
    WriteLineIndent("def has_flags(self, flags):");
    Indent(1);
    WriteLineIndent("return ((self.value & flags.value) != 0) and ((self.value & flags.value) == flags.value)");
    Indent(-1);

    // Generate flags set_flags() method
    WriteLine();
    WriteLineIndent("# Set flags");
    WriteLineIndent("def set_flags(self, flags):");
    Indent(1);
    WriteLineIndent("self.value |= flags.value");
    WriteLineIndent("return self");
    Indent(-1);

    // Generate flags remove_flags() method
    WriteLine();
    WriteLineIndent("# Remove flags");
    WriteLineIndent("def remove_flags(self, flags):");
    Indent(1);
    WriteLineIndent("self.value &= ~flags.value");
    WriteLineIndent("return self");
    Indent(-1);

    // Generate flags __format__ method
    WriteLine();
    WriteLineIndent("def __format__(self, format_spec):");
    Indent(1);
    WriteLineIndent("return self.__str__()");
    Indent(-1);

    // Generate flags __str__ method
    WriteLine();
    WriteLineIndent("def __str__(self):");
    Indent(1);
    WriteLineIndent("sb = list()");
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("first = True");
        for (auto it = f->body->values.begin(); it != f->body->values.end(); ++it)
        {
            WriteLineIndent("if (self.value & " + *f->name + "." + *(*it)->name + ".value) and ((self.value & " + *f->name + "." + *(*it)->name + ".value) == " + *f->name + "." + *(*it)->name + ".value):");
            Indent(1);
            WriteLineIndent("if first:");
            Indent(1);
            WriteLineIndent("# noinspection PyUnusedLocal");
            WriteLineIndent("first = False");
            Indent(-1);
            WriteLineIndent("else:");
            Indent(1);
            WriteLineIndent("sb.append(\"|\")");
            Indent(-1);
            WriteLineIndent("sb.append(\"" + *(*it)->name + "\")");
            Indent(-1);
        }
    }
    WriteLineIndent("return \"\".join(sb)");
    Indent(-1);

    if (JSON())
    {
        // Generate flags __from_json__ method
        WriteLine();
        WriteLineIndent("@staticmethod");
        WriteLineIndent("def __from_json__(value):");
        Indent(1);
        WriteLineIndent("if value is None:");
        Indent(1);
        WriteLineIndent("return None");
        Indent(-1);
        WriteLineIndent("return " + *f->name + "(value)");
        Indent(-1);
    }

    // Generate flags end
    Indent(-1);

    // Generate flags field model
    GenerateFlagsFieldModel(f);

    // Generate flags final model
    if (Final())
        GenerateFlagsFinalModel(f);
}

void GeneratorPython::GenerateFlagsFieldModel(const std::shared_ptr<FlagsType>& f)
{
    std::string code = R"CODE(

# Fast Binary Encoding _FLAGS_NAME_ field model
class FieldModel_FLAGS_NAME_(fbe.FieldModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the field size
    @property
    def fbe_size(self):
        return _FLAGS_SIZE_

    # Get the value
    def get(self, defaults=None):
        if defaults is None:
            defaults = _FLAGS_NAME_()

        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return defaults

        return _FLAGS_NAME_(self.read__FLAGS_TYPE_(self.fbe_offset))

    # Set the value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return

        self.write__FLAGS_TYPE_(self.fbe_offset, value)
)CODE";

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), *f->name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), ConvertEnumType(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateFlagsFinalModel(const std::shared_ptr<FlagsType>& f)
{
    std::string code = R"CODE(

# Fast Binary Encoding _FLAGS_NAME_ final model
class FinalModel_FLAGS_NAME_(fbe.FinalModel):
    def __init__(self, buffer, offset):
        super().__init__(buffer, offset)

    # Get the allocation size
    # noinspection PyUnusedLocal
    def fbe_allocation_size(self, value):
        return self.fbe_size

    # Get the final size
    @property
    def fbe_size(self):
        return _FLAGS_SIZE_

    # Check if the value is valid
    def verify(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return sys.maxsize

        return self.fbe_size

    # Get the value
    def get(self):
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return _FLAGS_NAME_(), 0

        return _FLAGS_NAME_(self.read__FLAGS_TYPE_(self.fbe_offset)), self.fbe_size

    # Set the value
    def set(self, value):
        assert ((self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size), "Model is broken!"
        if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:
            return 0

        self.write__FLAGS_TYPE_(self.fbe_offset, value)
        return self.fbe_size
)CODE";

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_FLAGS_NAME_"), *f->name);
    code = std::regex_replace(code, std::regex("_FLAGS_TYPE_"), ConvertEnumType(flags_type));
    code = std::regex_replace(code, std::regex("_FLAGS_SIZE_"), ConvertEnumSize(flags_type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorPython::GenerateStruct(const std::shared_ptr<StructType>& s)
{
    std::string base_type = (s->base && !s->base->empty()) ? ConvertTypeName(*s->base, false) : "object";

    // Generate struct begin
    WriteLine();
    WriteLine();
    WriteLineIndent("@functools.total_ordering");
    WriteLineIndent("class " + *s->name + "(" + base_type + "):");
    Indent(1);

    // Generate struct __slots__
    if (s->body && !s->body->fields.empty())
    {
        WriteIndent("__slots__ = ");
        for (const auto& field : s->body->fields)
            Write("\"" + *field->name + "\", ");
        WriteLine();
        WriteLine();
    }

    // Generate struct constructor
    WriteIndent("def __init__(self");
    if (s->base && !s->base->empty())
        Write(", parent=None");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            Write(", " + *field->name + "=");
            if (field->value && ((*field->value == "utc") || (*field->value == "uuid1") || (*field->value == "uuid2")))
                Write("None");
            else if (field->value)
                Write(ConvertConstant(*field->type, *field->value, field->optional));
            else if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                Write("None");
            else
                Write(ConvertDefaultOrNone(*field->type, field->optional));
        }
    }
    WriteLine("):");
    Indent(1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("super().__init__()");
        WriteLineIndent("if parent is None:");
        Indent(1);
        WriteLineIndent("parent = " + base_type + "()");
        Indent(-1);
        WriteLineIndent("super().copy(parent)");
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->array || field->vector || field->list || field->set || field->map || field->hash || (field->value && ((*field->value == "utc") || (*field->value == "uuid1") || (*field->value == "uuid2"))) || (!field->value && !field->optional && !IsPrimitiveType(*field->type) && (*field->type != "string") && (*field->type != "timestamp")))
            {
                WriteLineIndent("if " + *field->name + " is None:");
                Indent(1);
                if (field->value)
                    WriteLineIndent(*field->name + " = " + ConvertConstant(*field->type, *field->value, field->optional));
                else
                    WriteLineIndent(*field->name + " = " + ConvertDefault(*field));
                Indent(-1);
            }
        }
        for (const auto& field : s->body->fields)
            WriteLineIndent("self." + *field->name + " = " + *field->name);
    }
    Indent(-1);
    if ((!s->base || s->base->empty()) && (!s->body || s->body->fields.empty()))
    {
        Indent(1);
        WriteLineIndent("pass");
        Indent(-1);
    }

    // Generate struct copy() method
    WriteLine();
    WriteLineIndent("# Struct shallow copy");
    WriteLineIndent("def copy(self, other):");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("super().copy(other)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("self." + *field->name + " = other." + *field->name);
    WriteLineIndent("return self");
    Indent(-1);

    // Generate struct clone() method
    WriteLine();
    WriteLineIndent("# Struct deep clone");
    WriteLineIndent("def clone(self):");
    Indent(1);
    WriteLineIndent("# Serialize the struct to the FBE stream");
    WriteLineIndent("writer = " + *s->name + "Model(fbe.WriteBuffer())");
    WriteLineIndent("writer.serialize(self)");
    WriteLine();
    WriteLineIndent("# Deserialize the struct from the FBE stream");
    WriteLineIndent("reader = " + *s->name + "Model(fbe.ReadBuffer())");
    WriteLineIndent("reader.attach_buffer(writer.buffer)");
    WriteLineIndent("return reader.deserialize()[0]");
    Indent(-1);

    // Generate struct __eq__ method
    WriteLine();
    WriteLineIndent("def __eq__(self, other):");
    Indent(1);
    WriteLineIndent("if not isinstance(self, other.__class__):");
    Indent(1);
    WriteLineIndent("return NotImplemented");
    Indent(-1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if not super().__eq__(other):");
        Indent(1);
        WriteLineIndent("return False");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("if not self." + *field->name + " == other." + *field->name + ":");
                Indent(1);
                WriteLineIndent("return False");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return True");
    Indent(-1);

    // Generate struct __lt__ method
    WriteLine();
    WriteLineIndent("def __lt__(self, other):");
    Indent(1);
    WriteLineIndent("if not isinstance(self, other.__class__):");
    Indent(1);
    WriteLineIndent("return NotImplemented");
    Indent(-1);
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if super().__lt__(other):");
        Indent(1);
        WriteLineIndent("return True");
        Indent(-1);
        WriteLineIndent("if super().__eq__(other):");
        Indent(1);
        WriteLineIndent("return False");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("if self." + *field->name + " < other." + *field->name + ":");
                Indent(1);
                WriteLineIndent("return True");
                Indent(-1);
                WriteLineIndent("if self." + *field->name + " == other." + *field->name + ":");
                Indent(1);
                WriteLineIndent("return False");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return False");
    Indent(-1);

    // Generate struct __key__ property
    WriteLine();
    WriteLineIndent("@property");
    WriteLineIndent("def __key__(self):");
    Indent(1);
    WriteIndent("return ");
    if (s->base && !s->base->empty())
        Write("super().__key__ + (");
    bool empty = true;
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                Write("self." + *field->name + ", ");
                empty = false;
            }
        }
    }
    if (s->base && !s->base->empty())
        Write(")");
    else if (empty)
        Write("()");
    WriteLine();
    Indent(-1);

    // Generate struct __hash__ method
    WriteLine();
    WriteLineIndent("def __hash__(self):");
    Indent(1);
    WriteLineIndent("return hash(self.__key__)");
    Indent(-1);

    // Generate flags __format__ method
    WriteLine();
    WriteLineIndent("def __format__(self, format_spec):");
    Indent(1);
    WriteLineIndent("return self.__str__()");
    Indent(-1);

    // Generate struct __str__ method
    WriteLine();
    WriteLineIndent("def __str__(self):");
    Indent(1);
    WriteLineIndent("sb = list()");
    WriteLineIndent("sb.append(\"" + *s->name + "(\")");
    bool first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("sb.append(" + base_type + ".__str__(self))");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=\")");
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("sb.append(\"***\")");
            else if (field->array || field->vector)
            {
                WriteLineIndent("if self." + *field->name + " is not None:");
                Indent(1);
                WriteLineIndent("first = True");
                WriteLineIndent("sb.append(\"[\" + str(len(self." + *field->name + "))" + " + \"][\")");
                WriteLineIndent("for item in self." + *field->name + ":");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", field->optional, true);
                WriteLineIndent("first = False");
                Indent(-1);
                WriteLineIndent("sb.append(\"]\")");
                Indent(-1);
                WriteLineIndent("else:");
                Indent(1);
                WriteLineIndent("sb.append(\"[0][]\")");
                Indent(-1);
            }
            else if (field->list)
            {
                WriteLineIndent("if self." + *field->name + " is not None:");
                Indent(1);
                WriteLineIndent("first = True");
                WriteLineIndent("sb.append(\"[\" + str(len(self." + *field->name + "))" + " + \"]<\")");
                WriteLineIndent("for item in self." + *field->name + ":");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", field->optional, true);
                WriteLineIndent("first = False");
                Indent(-1);
                WriteLineIndent("sb.append(\">\")");
                Indent(-1);
                WriteLineIndent("else:");
                Indent(1);
                WriteLineIndent("sb.append(\"[0]<>\")");
                Indent(-1);
            }
            else if (field->set)
            {
                WriteLineIndent("if self." + *field->name + " is not None:");
                Indent(1);
                WriteLineIndent("first = True");
                WriteLineIndent("sb.append(\"[\" + str(len(self." + *field->name + "))" + " + \"]{\")");
                WriteLineIndent("for item in self." + *field->name + ":");
                Indent(1);
                WriteOutputStreamValue(*field->type, "item", field->optional, true);
                WriteLineIndent("first = False");
                Indent(-1);
                WriteLineIndent("sb.append(\"}\")");
                Indent(-1);
                WriteLineIndent("else:");
                Indent(1);
                WriteLineIndent("sb.append(\"[0]{}\")");
                Indent(-1);
            }
            else if (field->map)
            {
                WriteLineIndent("if self." + *field->name + " is not None:");
                Indent(1);
                WriteLineIndent("first = True");
                WriteLineIndent("sb.append(\"[\" + str(len(self." + *field->name + "))" + " + \"]<{\")");
                WriteLineIndent("for key, value in self." + *field->name + ".items():");
                Indent(1);
                WriteOutputStreamValue(*field->key, "key", false, true);
                WriteLineIndent("sb.append(\"->\")");
                WriteOutputStreamValue(*field->type, "value", field->optional, false);
                WriteLineIndent("first = False");
                Indent(-1);
                WriteLineIndent("sb.append(\"}>\")");
                Indent(-1);
                WriteLineIndent("else:");
                Indent(1);
                WriteLineIndent("sb.append(\"[0]<{}>\")");
                Indent(-1);
            }
            else if (field->hash)
            {
                WriteLineIndent("if self." + *field->name + " is not None:");
                Indent(1);
                WriteLineIndent("first = True");
                WriteLineIndent("sb.append(\"[\" + str(len(self." + *field->name + "))" + " + \"][{\")");
                WriteLineIndent("for key, value in self." + *field->name + ".items():");
                Indent(1);
                WriteOutputStreamValue(*field->key, "key", false, true);
                WriteLineIndent("sb.append(\"->\")");
                WriteOutputStreamValue(*field->type, "value", field->optional, false);
                WriteLineIndent("first = False");
                Indent(-1);
                WriteLineIndent("sb.append(\"}]\")");
                Indent(-1);
                WriteLineIndent("else:");
                Indent(1);
                WriteLineIndent("sb.append(\"[0][{}]\")");
                Indent(-1);
            }
            else
                WriteOutputStreamValue(*field->type, "self." + *field->name, field->optional, false);
            first = false;
        }
    }
    WriteLineIndent("sb.append(\")\")");
    WriteLineIndent("return \"\".join(sb)");
    Indent(-1);

    if (JSON())
    {
        // Generate struct to_json method
        WriteLine();
        WriteLineIndent("# Get struct JSON value");
        WriteLineIndent("def to_json(self):");
        Indent(1);
        WriteLineIndent("return json.dumps(self.__to_json__(), cls=fbe.JSONEncoder, separators=(',', ':'))");
        Indent(-1);

        // Generate struct __to_json__ method
        WriteLine();
        WriteLineIndent("def __to_json__(self):");
        Indent(1);
        WriteLineIndent("result = dict()");
        if (s->base && !s->base->empty())
            WriteLineIndent("result.update(super().__to_json__())");
        if (s->body)
        {
            WriteLineIndent("result.update(dict(");
            Indent(1);
            for (const auto& field : s->body->fields)
            {
                if ((*field->type == "char") || (*field->type == "wchar"))
                    WriteLineIndent(*field->name + "=None if self." + *field->name + " is None else ord(self." + *field->name + "), ");
                else
                    WriteLineIndent(*field->name + "=self." + *field->name + ", ");
            }
            Indent(-1);
            WriteLineIndent("))");
        }
        WriteLineIndent("return result");
        Indent(-1);

        // Generate struct from_json method
        WriteLine();
        WriteLineIndent("# Create struct from JSON value");
        WriteLineIndent("@staticmethod");
        WriteLineIndent("def from_json(document):");
        Indent(1);
        WriteLineIndent("return " + *s->name + ".__from_json__(json.loads(document))");
        Indent(-1);

        // Generate struct __from_json__ method
        WriteLine();
        WriteLineIndent("@staticmethod");
        WriteLineIndent("def __from_json__(fields):");
        Indent(1);
        WriteLineIndent("if fields is None:");
        Indent(1);
        WriteLineIndent("return None");
        Indent(-1);
        WriteLineIndent("return " + *s->name + "(");
        Indent(1);
        if (s->base && !s->base->empty())
            WriteLineIndent(base_type + ".__from_json__(fields),");
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                std::string key;
                if (field->key)
                {
                    if ((*field->key == "char") || (*field->key == "wchar"))
                        key = "None if key is None else chr(key)";
                    else if (*field->key == "bytes")
                        key = "None if key is None else base64.b64decode(key.encode('ascii'))";
                    else if (*field->key == "decimal")
                        key = "None if key is None else decimal.Decimal(key)";
                    else if (*field->key == "uuid")
                        key = "None if key is None else uuid.UUID(key)";
                    else if (IsPythonType(*field->key))
                        key = "key";
                    else
                        key = *field->key + ".__from_json__(key)";
                }

                std::string value;
                if (field->type)
                {
                    if ((*field->type == "char") || (*field->type == "wchar"))
                        value = "None if value is None else chr(value)";
                    else if (*field->type == "bytes")
                        value = "None if value is None else base64.b64decode(value.encode('ascii'))";
                    else if (*field->type == "decimal")
                        value = "None if value is None else decimal.Decimal(value)";
                    else if (*field->type == "uuid")
                        value = "None if value is None else uuid.UUID(value)";
                    else if (IsPythonType(*field->type))
                        value = "value";
                    else
                        value = *field->type + ".__from_json__(value)";
                }

                if (field->array || field->vector || field->list)
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else [" + value + " for value in fields[\"" + *field->name + "\"]],");
                else if (field->set)
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else {" + value + " for value in fields[\"" + *field->name + "\"]},");
                else if (field->map || field->hash)
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else {" + key + ": " + value + " for key, value in fields[\"" + *field->name + "\"].items()},");
                else if ((*field->type == "char") || (*field->type == "wchar"))
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else None if fields[\"" + *field->name + "\"] is None else chr(fields[\"" + *field->name + "\"]),");
                else if (*field->type == "bytes")
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else None if fields[\"" + *field->name + "\"] is None else base64.b64decode(fields[\"" + *field->name + "\"].encode('ascii')),");
                else if (*field->type == "decimal")
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else None if fields[\"" + *field->name + "\"] is None else decimal.Decimal(fields[\"" + *field->name + "\"]),");
                else if (*field->type == "uuid")
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else None if fields[\"" + *field->name + "\"] is None else uuid.UUID(fields[\"" + *field->name + "\"]),");
                else if (IsPythonType(*field->type))
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else fields[\"" + *field->name + "\"],");
                else
                    WriteLineIndent("None if \"" + *field->name + "\" not in fields else " + *field->type + ".__from_json__(fields[\"" + *field->name + "\"]),");
            }
        }
        Indent(-1);
        WriteLineIndent(")");
        Indent(-1);
    }

    // Generate struct FBE type property
    WriteLine();
    WriteLineIndent("# Get the FBE type");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_type(self):");
    Indent(1);
    WriteLineIndent("return self.TYPE");
    Indent(-1);

    // Generate struct FBE type
    WriteLine();
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("TYPE = " + base_type + ".TYPE");
    else
        WriteLineIndent("TYPE = " + std::to_string(s->type));

    // Generate struct end
    Indent(-1);

    // Generate struct field model
    GenerateStructFieldModel(s);

    // Generate struct model
    GenerateStructModel(s);

    // Generate struct final models
    if (Final())
    {
        GenerateStructFinalModel(s);
        GenerateStructModelFinal(s);
    }
}

void GeneratorPython::GenerateStructFieldModel(const std::shared_ptr<StructType>& s)
{
    // Generate struct field model begin
    WriteLine();
    WriteLine();
    WriteLineIndent("class FieldModel" + *s->name + "(fbe.FieldModel):");
    Indent(1);

    // Generate struct field model __slots__
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteIndent("__slots__ = ");
        if (s->base && !s->base->empty())
            Write("\"_parent\", ");
        if (s->body)
            for (const auto& field : s->body->fields)
                Write("\"_" + *field->name + "\", ");
        WriteLine();
        WriteLine();
    }

    // Generate struct field model constructor
    WriteLineIndent("def __init__(self, buffer, offset):");
    Indent(1);
    WriteLineIndent("super().__init__(buffer, offset)");
    std::string prev_offset("4");
    std::string prev_size("4");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("self._parent = " + ConvertTypeFieldName(*s->base, false) + "(buffer, " + prev_offset + " + " + prev_size + ")");
        prev_offset = "self._parent.fbe_offset";
        prev_size = "self._parent.fbe_body - 4 - 4";
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("self._" + *field->name + " = " + ConvertTypeFieldInitialization(*field, prev_offset + " + " + prev_size, false));
            prev_offset = "self._" + *field->name + ".fbe_offset";
            prev_size = "self._" + *field->name + ".fbe_size";
        }
    }
    Indent(-1);

    // Generate struct field model accessors
    if (s->base && !s->base->empty())
    {
        WriteLine();
        WriteLineIndent("@property");
        WriteLineIndent("def parent(self):");
        Indent(1);
        WriteLineIndent("return self._parent");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLine();
            WriteLineIndent("@property");
            WriteLineIndent("def " + *field->name + "(self):");
            Indent(1);
            WriteLineIndent("return self._" + *field->name);
            Indent(-1);
        }
    }

    // Generate struct field model FBE properties
    WriteLine();
    WriteLineIndent("# Get the field size");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_size(self):");
    Indent(1);
    WriteLineIndent("return 4");
    Indent(-1);
    WriteLine();
    WriteLineIndent("# Get the field body size");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_body(self):");
    Indent(1);
    WriteLineIndent("fbe_result = 4 + 4 \\");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ self.parent.fbe_body - 4 - 4 \\");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ self." + *field->name + ".fbe_size \\");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return fbe_result");
    Indent(-1);
    WriteLine();
    WriteLineIndent("# Get the field extra size");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_extra(self):");
    Indent(1);
    WriteLineIndent("if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_offset = self.read_uint32(self.fbe_offset)");
    WriteLineIndent("if (fbe_struct_offset == 0) or ((self._buffer.offset + fbe_struct_offset + 4) > self._buffer.size):");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self._buffer.shift(fbe_struct_offset)");
    WriteLine();
    WriteLineIndent("fbe_result = self.fbe_body \\");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ self.parent.fbe_extra \\");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ self." + *field->name + ".fbe_extra \\");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self._buffer.unshift(fbe_struct_offset)");
    WriteLine();
    WriteLineIndent("return fbe_result");
    Indent(-1);
    WriteLine();
    WriteLineIndent("# Get the field type");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_type(self):");
    Indent(1);
    WriteLineIndent("return self.TYPE");
    Indent(-1);

    // Generate struct field model type
    WriteLine();
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("TYPE = " + ConvertTypeFieldName(*s->base, false) + ".TYPE");
    else
        WriteLineIndent("TYPE = " + std::to_string(s->type));

    // Generate struct field model verify() method
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify(self, fbe_verify_type=True):");
    Indent(1);
    WriteLineIndent("if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:");
    Indent(1);
    WriteLineIndent("return True");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_offset = self.read_uint32(self.fbe_offset)");
    WriteLineIndent("if (fbe_struct_offset == 0) or ((self._buffer.offset + fbe_struct_offset + 4 + 4) > self._buffer.size):");
    Indent(1);
    WriteLineIndent("return False");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = self.read_uint32(fbe_struct_offset)");
    WriteLineIndent("if fbe_struct_size < (4 + 4):");
    Indent(1);
    WriteLineIndent("return False");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_type = self.read_uint32(fbe_struct_offset + 4)");
    WriteLineIndent("if fbe_verify_type and (fbe_struct_type != self.fbe_type):");
    Indent(1);
    WriteLineIndent("return False");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self._buffer.shift(fbe_struct_offset)");
    WriteLineIndent("fbe_result = self.verify_fields(fbe_struct_size)");
    WriteLineIndent("self._buffer.unshift(fbe_struct_offset)");
    WriteLineIndent("return fbe_result");
    Indent(-1);

    // Generate struct field model verify_fields() method
    WriteLine();
    WriteLineIndent("# Check if the struct fields are valid");
    WriteLineIndent("def verify_fields(self, fbe_struct_size):");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_size = 4 + 4");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if (fbe_current_size + self.parent.fbe_body - 4 - 4) > fbe_struct_size:");
            Indent(1);
            WriteLineIndent("return True");
            Indent(-1);
            WriteLineIndent("if not self.parent.verify_fields(fbe_struct_size):");
            Indent(1);
            WriteLineIndent("return False");
            Indent(-1);
            WriteLineIndent("fbe_current_size += self.parent.fbe_body - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if (fbe_current_size + self." + *field->name + ".fbe_size) > fbe_struct_size:");
                Indent(1);
                WriteLineIndent("return True");
                Indent(-1);
                WriteLineIndent("if not self." + *field->name + ".verify():");
                Indent(1);
                WriteLineIndent("return False");
                Indent(-1);
                WriteLineIndent("fbe_current_size += self." + *field->name + ".fbe_size");
            }
        }
        WriteLine();
    }
    WriteLineIndent("return True");
    Indent(-1);

    // Generate struct field model get_begin() method
    WriteLine();
    WriteLineIndent("# Get the struct value (begin phase)");
    WriteLineIndent("def get_begin(self):");
    Indent(1);
    WriteLineIndent("if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_offset = self.read_uint32(self.fbe_offset)");
    WriteLineIndent("assert (fbe_struct_offset > 0) and ((self._buffer.offset + fbe_struct_offset + 4 + 4) <= self._buffer.size), \"Model is broken!\"");
    WriteLineIndent("if (fbe_struct_offset == 0) or ((self._buffer.offset + fbe_struct_offset + 4 + 4) > self._buffer.size):");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = self.read_uint32(fbe_struct_offset)");
    WriteLineIndent("assert (fbe_struct_size >= (4 + 4)), \"Model is broken!\"");
    WriteLineIndent("if fbe_struct_size < (4 + 4):");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self._buffer.shift(fbe_struct_offset)");
    WriteLineIndent("return fbe_struct_offset");
    Indent(-1);

    // Generate struct field model get_end() method
    WriteLine();
    WriteLineIndent("# Get the struct value (end phase)");
    WriteLineIndent("def get_end(self, fbe_begin):");
    Indent(1);
    WriteLineIndent("self._buffer.unshift(fbe_begin)");
    Indent(-1);

    // Generate struct field model get() method
    WriteLine();
    WriteLineIndent("# Get the struct value");
    WriteLineIndent("def get(self, fbe_value=None):");
    Indent(1);
    WriteLineIndent("if fbe_value is None:");
    Indent(1);
    WriteLineIndent("fbe_value = " + *s->name + "()");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_begin = self.get_begin()");
    WriteLineIndent("if fbe_begin == 0:");
    Indent(1);
    WriteLineIndent("return fbe_value");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = self.read_uint32(0)");
    WriteLineIndent("self.get_fields(fbe_value, fbe_struct_size)");
    WriteLineIndent("self.get_end(fbe_begin)");
    WriteLineIndent("return fbe_value");
    Indent(-1);

    // Generate struct field model get_fields() method
    WriteLine();
    WriteLineIndent("# Get the struct fields values");
    WriteLineIndent("def get_fields(self, fbe_value, fbe_struct_size):");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_size = 4 + 4");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if (fbe_current_size + self.parent.fbe_body - 4 - 4) <= fbe_struct_size:");
            Indent(1);
            WriteLineIndent("self.parent.get_fields(fbe_value, fbe_struct_size)");
            Indent(-1);
            WriteLineIndent("fbe_current_size += self.parent.fbe_body - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if (fbe_current_size + self." + *field->name + ".fbe_size) <= fbe_struct_size:");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("self." + *field->name + ".get(fbe_value." + *field->name + ")");
                else
                    WriteLineIndent("fbe_value." + *field->name + " = self." + *field->name + ".get(" + (field->value ? ConvertConstant(*field->type, *field->value, field->optional) : "") + ")");
                Indent(-1);
                WriteLineIndent("else:");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbe_value." + *field->name + ".clear()");
                else
                    WriteLineIndent("fbe_value." + *field->name + " = " + ConvertDefault(*field));
                Indent(-1);
                WriteLineIndent("fbe_current_size += self." + *field->name + ".fbe_size");
            }
        }
    }
    else
        WriteLineIndent("pass");
    Indent(-1);

    // Generate struct field model set_begin() method
    WriteLine();
    WriteLineIndent("# Set the struct value (begin phase)");
    WriteLineIndent("def set_begin(self):");
    Indent(1);
    WriteLineIndent("assert (self._buffer.offset + self.fbe_offset + self.fbe_size) <= self._buffer.size, \"Model is broken!\"");
    WriteLineIndent("if (self._buffer.offset + self.fbe_offset + self.fbe_size) > self._buffer.size:");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = self.fbe_body");
    WriteLineIndent("fbe_struct_offset = self._buffer.allocate(fbe_struct_size) - self._buffer.offset");
    WriteLineIndent("assert (fbe_struct_offset > 0) and ((self._buffer.offset + fbe_struct_offset + fbe_struct_size) <= self._buffer.size), \"Model is broken!\"");
    WriteLineIndent("if (fbe_struct_offset <= 0) or ((self._buffer.offset + fbe_struct_offset + fbe_struct_size) > self._buffer.size):");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self.write_uint32(self.fbe_offset, fbe_struct_offset)");
    WriteLineIndent("self.write_uint32(fbe_struct_offset, fbe_struct_size)");
    WriteLineIndent("self.write_uint32(fbe_struct_offset + 4, self.fbe_type)");
    WriteLine();
    WriteLineIndent("self._buffer.shift(fbe_struct_offset)");
    WriteLineIndent("return fbe_struct_offset");
    Indent(-1);

    // Generate struct field model set_end() method
    WriteLine();
    WriteLineIndent("# Set the struct value (end phase)");
    WriteLineIndent("def set_end(self, fbe_begin):");
    Indent(1);
    WriteLineIndent("self._buffer.unshift(fbe_begin)");
    Indent(-1);

    // Generate struct field model set() method
    WriteLine();
    WriteLineIndent("# Set the struct value");
    WriteLineIndent("def set(self, fbe_value):");
    Indent(1);
    WriteLineIndent("fbe_begin = self.set_begin()");
    WriteLineIndent("if fbe_begin == 0:");
    Indent(1);
    WriteLineIndent("return");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self.set_fields(fbe_value)");
    WriteLineIndent("self.set_end(fbe_begin)");
    Indent(-1);

    // Generate struct field model set_fields() method
    WriteLine();
    WriteLineIndent("# Set the struct fields values");
    WriteLineIndent("def set_fields(self, fbe_value):");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        if (s->base && !s->base->empty())
            WriteLineIndent("self.parent.set_fields(fbe_value)");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent("self." + *field->name + ".set(fbe_value." + *field->name + ")");
    }
    else
        WriteLineIndent("pass");
    Indent(-1);

    // Generate struct field model end
    Indent(-1);
}

void GeneratorPython::GenerateStructModel(const std::shared_ptr<StructType>& s)
{
    // Generate struct model begin
    WriteLine();
    WriteLine();
    WriteLineIndent("# Fast Binary Encoding " + *s->name + " model");
    WriteLineIndent("class " + *s->name + "Model(fbe.Model):");
    Indent(1);

    // Generate struct model __slots__
    WriteLineIndent("__slots__ = \"_model\",");

    // Generate struct model constructor
    WriteLine();
    WriteLineIndent("def __init__(self, buffer=None):");
    Indent(1);
    WriteLineIndent("super().__init__(buffer)");
    WriteLineIndent("self._model = FieldModel" + *s->name + "(self.buffer, 4)");
    Indent(-1);

    // Generate struct model accessor
    WriteLine();
    WriteLineIndent("@property");
    WriteLineIndent("def model(self):");
    Indent(1);
    WriteLineIndent("return self._model");
    Indent(-1);

    // Generate struct model FBE properties
    WriteLine();
    WriteLineIndent("# Get the model size");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_size(self):");
    Indent(1);
    WriteLineIndent("return self._model.fbe_size + self._model.fbe_extra");
    Indent(-1);
    WriteLine();
    WriteLineIndent("# Get the model type");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_type(self):");
    Indent(1);
    WriteLineIndent("return self.TYPE");
    Indent(-1);

    // Generate struct model type
    WriteLine();
    WriteLineIndent("TYPE = FieldModel" + *s->name + ".TYPE");

    // Generate struct model verify() method
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify(self):");
    Indent(1);
    WriteLineIndent("if (self.buffer.offset + self._model.fbe_offset - 4) > self.buffer.size:");
    Indent(1);
    WriteLineIndent("return False");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_full_size = self.read_uint32(self._model.fbe_offset - 4)");
    WriteLineIndent("if fbe_full_size < self._model.fbe_size:");
    Indent(1);
    WriteLineIndent("return False");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return self._model.verify()");
    Indent(-1);

    // Generate struct model create_begin() method
    WriteLine();
    WriteLineIndent("# Create a new model (begin phase)");
    WriteLineIndent("def create_begin(self):");
    Indent(1);
    WriteLineIndent("fbe_begin = self.buffer.allocate(4 + self._model.fbe_size)");
    WriteLineIndent("return fbe_begin");
    Indent(-1);

    // Generate struct model create_end() method
    WriteLine();
    WriteLineIndent("# Create a new model (end phase)");
    WriteLineIndent("def create_end(self, fbe_begin):");
    Indent(1);
    WriteLineIndent("fbe_end = self.buffer.size");
    WriteLineIndent("fbe_full_size = fbe_end - fbe_begin");
    WriteLineIndent("self.write_uint32(self._model.fbe_offset - 4, fbe_full_size)");
    WriteLineIndent("return fbe_full_size");
    Indent(-1);

    // Generate struct model serialize() method
    WriteLine();
    WriteLineIndent("# Serialize the struct value");
    WriteLineIndent("def serialize(self, value):");
    Indent(1);
    WriteLineIndent("fbe_begin = self.create_begin()");
    WriteLineIndent("self._model.set(value)");
    WriteLineIndent("fbe_full_size = self.create_end(fbe_begin)");
    WriteLineIndent("return fbe_full_size");
    Indent(-1);

    // Generate struct model deserialize() methods
    WriteLine();
    WriteLineIndent("# Deserialize the struct value");
    WriteLineIndent("def deserialize(self, value=None):");
    Indent(1);
    WriteLineIndent("if value is None:");
    Indent(1);
    WriteLineIndent("value = " + *s->name + "()");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (self.buffer.offset + self._model.fbe_offset - 4) > self.buffer.size:");
    Indent(1);
    WriteLineIndent("value = " + *s->name + "()");
    WriteLineIndent("return value, 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_full_size = self.read_uint32(self._model.fbe_offset - 4)");
    WriteLineIndent("assert (fbe_full_size >= self._model.fbe_size), \"Model is broken!\"");
    WriteLineIndent("if fbe_full_size < self._model.fbe_size:");
    Indent(1);
    WriteLineIndent("value = " + *s->name + "()");
    WriteLineIndent("return value, 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self._model.get(value)");
    WriteLineIndent("return value, fbe_full_size");
    Indent(-1);

    // Generate struct model next() method
    WriteLine();
    WriteLineIndent("# Move to the next struct value");
    WriteLineIndent("def next(self, prev):");
    Indent(1);
    WriteLineIndent("self._model.fbe_shift(prev)");
    Indent(-1);

    // Generate struct model end
    Indent(-1);
}

void GeneratorPython::GenerateStructFinalModel(const std::shared_ptr<StructType>& s)
{
    // Generate struct final model begin
    WriteLine();
    WriteLine();
    WriteLineIndent("class FinalModel" + *s->name + "(fbe.FinalModel):");
    Indent(1);

    // Generate struct final model __slots__
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteIndent("__slots__ = ");
        if (s->base && !s->base->empty())
            Write("\"_parent\", ");
        if (s->body)
            for (const auto& field : s->body->fields)
                Write("\"_" + *field->name + "\", ");
        WriteLine();
        WriteLine();
    }

    // Generate struct final model constructor
    WriteLineIndent("def __init__(self, buffer, offset):");
    Indent(1);
    WriteLineIndent("super().__init__(buffer, offset)");
    if (s->base && !s->base->empty())
        WriteLineIndent("self._parent = " + ConvertTypeFieldName(*s->base, true) + "(buffer, 0)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("self._" + *field->name + " = " + ConvertTypeFieldInitialization(*field, "0", true));
    Indent(-1);

    // Generate struct final model accessors
    if (s->base && !s->base->empty())
    {
        WriteLine();
        WriteLineIndent("@property");
        WriteLineIndent("def parent(self):");
        Indent(1);
        WriteLineIndent("return self._parent");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLine();
            WriteLineIndent("@property");
            WriteLineIndent("def " + *field->name + "(self):");
            Indent(1);
            WriteLineIndent("return self._" + *field->name);
            Indent(-1);
        }
    }

    // Generate struct final model FBE properties
    WriteLine();
    WriteLineIndent("# Get the allocation size");
    WriteLineIndent("def fbe_allocation_size(self, fbe_value):");
    Indent(1);
    WriteLineIndent("fbe_result = 0 \\");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ self.parent.fbe_allocation_size(fbe_value) \\");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ self." + *field->name + ".fbe_allocation_size(fbe_value." + *field->name + ") \\");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return fbe_result");
    Indent(-1);
    WriteLine();
    WriteLineIndent("# Get the final type");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_type(self):");
    Indent(1);
    WriteLineIndent("return self.TYPE");
    Indent(-1);

    // Generate struct final model type
    WriteLine();
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("TYPE = " + ConvertTypeFieldName(*s->base, true) + ".TYPE");
    else
        WriteLineIndent("TYPE = " + std::to_string(s->type));

    // Generate struct final model verify() method
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify(self):");
    Indent(1);
    WriteLineIndent("self._buffer.shift(self.fbe_offset)");
    WriteLineIndent("fbe_result = self.verify_fields()");
    WriteLineIndent("self._buffer.unshift(self.fbe_offset)");
    WriteLineIndent("return fbe_result");
    Indent(-1);

    // Generate struct final model verify_fields() method
    WriteLine();
    WriteLineIndent("# Check if the struct fields are valid");
    WriteLineIndent("def verify_fields(self):");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_offset = 0");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("self.parent.fbe_offset = fbe_current_offset");
            WriteLineIndent("fbe_field_size = self.parent.verify_fields()");
            WriteLineIndent("if fbe_field_size == sys.maxsize:");
            Indent(1);
            WriteLineIndent("return sys.maxsize");
            Indent(-1);
            WriteLineIndent("fbe_current_offset += fbe_field_size");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("self." + *field->name + ".fbe_offset = fbe_current_offset");
                WriteLineIndent("fbe_field_size = self." + *field->name + ".verify()");
                WriteLineIndent("if fbe_field_size == sys.maxsize:");
                Indent(1);
                WriteLineIndent("return sys.maxsize");
                Indent(-1);
                WriteLineIndent("fbe_current_offset += fbe_field_size");
            }
        }
        WriteLine();
        WriteLineIndent("return fbe_current_offset");
    }
    else
        WriteLineIndent("return 0");
    Indent(-1);

    // Generate struct final model get() method
    WriteLine();
    WriteLineIndent("# Get the struct value");
    WriteLineIndent("def get(self, fbe_value=None):");
    Indent(1);
    WriteLineIndent("if fbe_value is None:");
    Indent(1);
    WriteLineIndent("fbe_value = " + *s->name + "()");
    Indent(-1);
    WriteLine();
    WriteLineIndent("self._buffer.shift(self.fbe_offset)");
    WriteLineIndent("fbe_size = self.get_fields(fbe_value)");
    WriteLineIndent("self._buffer.unshift(self.fbe_offset)");
    WriteLineIndent("return fbe_value, fbe_size");
    Indent(-1);

    // Generate struct final model get_fields() method
    WriteLine();
    WriteLineIndent("# Get the struct fields values");
    WriteLineIndent("def get_fields(self, fbe_value):");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_offset = 0");
        WriteLineIndent("fbe_current_size = 0");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("self.parent.fbe_offset = fbe_current_offset");
            WriteLineIndent("fbe_result = self.parent.get_fields(fbe_value)");
            WriteLineIndent("fbe_current_offset += fbe_result");
            WriteLineIndent("fbe_current_size += fbe_result");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("self." + *field->name + ".fbe_offset = fbe_current_offset");
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbe_result = self." + *field->name + ".get(fbe_value." + *field->name + ")");
                else
                {
                    WriteLineIndent("fbe_result = self." + *field->name + ".get()");
                    WriteLineIndent("fbe_value." + *field->name + " = fbe_result[0]");
                }
                WriteLineIndent("fbe_current_offset += fbe_result[1]");
                WriteLineIndent("fbe_current_size += fbe_result[1]");
            }
        }
        WriteLine();
        WriteLineIndent("return fbe_current_size");
    }
    else
        WriteLineIndent("return 0");
    Indent(-1);

    // Generate struct final model set() method
    WriteLine();
    WriteLineIndent("# Set the struct value");
    WriteLineIndent("def set(self, fbe_value):");
    Indent(1);
    WriteLineIndent("self._buffer.shift(self.fbe_offset)");
    WriteLineIndent("fbe_size = self.set_fields(fbe_value)");
    WriteLineIndent("self._buffer.unshift(self.fbe_offset)");
    WriteLineIndent("return fbe_size");
    Indent(-1);

    // Generate struct final model set_fields() method
    WriteLine();
    WriteLineIndent("# Set the struct fields values");
    WriteLineIndent("def set_fields(self, fbe_value):");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("fbe_current_offset = 0");
        WriteLineIndent("fbe_current_size = 0");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("self.parent.fbe_offset = fbe_current_offset");
            WriteLineIndent("fbe_field_size = self.parent.set_fields(fbe_value)");
            WriteLineIndent("fbe_current_offset += fbe_field_size");
            WriteLineIndent("fbe_current_size += fbe_field_size");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("self." + *field->name + ".fbe_offset = fbe_current_offset");
                WriteLineIndent("fbe_field_size = self." + *field->name + ".set(fbe_value." + *field->name + ")");
                WriteLineIndent("fbe_current_offset += fbe_field_size");
                WriteLineIndent("fbe_current_size += fbe_field_size");
            }
        }
        WriteLine();
        WriteLineIndent("return fbe_current_size");
    }
    else
        WriteLineIndent("return 0");
    Indent(-1);

    // Generate struct final model end
    Indent(-1);
}

void GeneratorPython::GenerateStructModelFinal(const std::shared_ptr<StructType>& s)
{
    // Generate struct model final begin
    WriteLine();
    WriteLine();
    WriteLineIndent("# Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("class " + *s->name + "FinalModel(fbe.Model):");
    Indent(1);

    // Generate struct model final __slots__
    WriteLineIndent("__slots__ = \"_model\",");

    // Generate struct model final constructor
    WriteLine();
    WriteLineIndent("def __init__(self, buffer=None):");
    Indent(1);
    WriteLineIndent("super().__init__(buffer)");
    WriteLineIndent("self._model = FinalModel" + *s->name + "(self.buffer, 8)");
    Indent(-1);

    // Generate struct model final FBE properties
    WriteLine();
    WriteLineIndent("# Get the model type");
    WriteLineIndent("@property");
    WriteLineIndent("def fbe_type(self):");
    Indent(1);
    WriteLineIndent("return self.TYPE");
    Indent(-1);

    // Generate struct model type
    WriteLine();
    WriteLineIndent("TYPE = FinalModel" + *s->name + ".TYPE");

    // Generate struct model final verify() method
    WriteLine();
    WriteLineIndent("# Check if the struct value is valid");
    WriteLineIndent("def verify(self):");
    Indent(1);
    WriteLineIndent("if (self.buffer.offset + self._model.fbe_offset) > self.buffer.size:");
    Indent(1);
    WriteLineIndent("return False");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = self.read_uint32(self._model.fbe_offset - 8)");
    WriteLineIndent("fbe_struct_type = self.read_uint32(self._model.fbe_offset - 4)");
    WriteLineIndent("if (fbe_struct_size <= 0) or (fbe_struct_type != self.fbe_type):");
    Indent(1);
    WriteLineIndent("return False");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return (8 + self._model.verify()) == fbe_struct_size");
    Indent(-1);

    // Generate struct model final serialize() method
    WriteLine();
    WriteLineIndent("# Serialize the struct value");
    WriteLineIndent("def serialize(self, value):");
    Indent(1);
    WriteLineIndent("fbe_initial_size = self.buffer.size");
    WriteLine();
    WriteLineIndent("fbe_struct_type = self.fbe_type");
    WriteLineIndent("fbe_struct_size = 8 + self._model.fbe_allocation_size(value)");
    WriteLineIndent("fbe_struct_offset = self.buffer.allocate(fbe_struct_size) - self.buffer.offset");
    WriteLineIndent("assert ((self.buffer.offset + fbe_struct_offset + fbe_struct_size) <= self.buffer.size), \"Model is broken!\"");
    WriteLineIndent("if (self.buffer.offset + fbe_struct_offset + fbe_struct_size) > self.buffer.size:");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = 8 + self._model.set(value)");
    WriteLineIndent("self.buffer.resize(fbe_initial_size + fbe_struct_size)");
    WriteLine();
    WriteLineIndent("self.write_uint32(self._model.fbe_offset - 8, fbe_struct_size)");
    WriteLineIndent("self.write_uint32(self._model.fbe_offset - 4, fbe_struct_type)");
    WriteLine();
    WriteLineIndent("return fbe_struct_size");
    Indent(-1);

    // Generate struct model final deserialize() methods
    WriteLine();
    WriteLineIndent("# Deserialize the struct value");
    WriteLineIndent("def deserialize(self, value=None):");
    Indent(1);
    WriteLineIndent("if value is None:");
    Indent(1);
    WriteLineIndent("value = " + *s->name + "()");
    Indent(-1);
    WriteLine();
    WriteLineIndent("assert ((self.buffer.offset + self._model.fbe_offset) <= self.buffer.size), \"Model is broken!\"");
    WriteLineIndent("if (self.buffer.offset + self._model.fbe_offset) > self.buffer.size:");
    Indent(1);
    WriteLineIndent("return " + *s->name + "(), 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_struct_size = self.read_uint32(self._model.fbe_offset - 8)");
    WriteLineIndent("fbe_struct_type = self.read_uint32(self._model.fbe_offset - 4)");
    WriteLineIndent("assert ((fbe_struct_size > 0) and (fbe_struct_type == self.fbe_type)), \"Model is broken!\"");
    WriteLineIndent("if (fbe_struct_size <= 0) or (fbe_struct_type != self.fbe_type):");
    Indent(1);
    WriteLineIndent("return " + *s->name + "(), 8");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbe_result = self._model.get(value)");
    WriteLineIndent("return fbe_result[0], (8 + fbe_result[1])");
    Indent(-1);

    // Generate struct model final next() method
    WriteLine();
    WriteLineIndent("# Move to the next struct value");
    WriteLineIndent("def next(self, prev):");
    Indent(1);
    WriteLineIndent("self._model.fbe_shift(prev)");
    Indent(-1);

    // Generate struct model final end
    Indent(-1);
}

void GeneratorPython::GenerateProtocolVersion(const std::shared_ptr<Package>& p)
{
    // Generate protocol version class
    WriteLine();
    WriteLine();
    WriteLineIndent("# Fast Binary Encoding " + *p->name + " protocol version");
    WriteLineIndent("class ProtocolVersion(object):");
    Indent(1);
    WriteLineIndent("# Protocol major version");
    WriteLineIndent("Major = " + std::to_string(p->version->major));
    WriteLineIndent("# Protocol minor version");
    WriteLineIndent("Minor = " + std::to_string(p->version->minor));
    Indent(-1);
}

void GeneratorPython::GenerateSender(const std::shared_ptr<Package>& p, bool final)
{
    std::string sender = (final ? "FinalSender" : "Sender");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate sender begin
    WriteLine();
    WriteLine();
    if (final)
        WriteLineIndent("# Fast Binary Encoding " + *p->name + " final sender");
    else
        WriteLineIndent("# Fast Binary Encoding " + *p->name + " sender");
    WriteLineIndent("class " + sender + "(fbe.Sender):");
    Indent(1);

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate sender __slots__
    if (p->import || messages)
    {
        WriteIndent("__slots__ = ");
        if (p->import)
        {
            for (const auto& import : p->import->imports)
                Write("\"_" + CppCommon::StringUtils::ToLower(*import) + "_sender\", ");
        }
        if (p->body)
        {
            for (const auto& s : p->body->structs)
                if (s->message)
                    Write("\"_" + CppCommon::StringUtils::ToLower(*s->name) + "_model\", ");
        }
        WriteLine();
        WriteLine();
    }

    // Generate sender constructor
    WriteLineIndent("def __init__(self, buffer=None):");
    Indent(1);
    WriteLineIndent("super().__init__(buffer, " + std::string(final ? "True" : "False") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*import) + "_sender = " + *import + "." + sender + "(self.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model = " + *s->name + model + "(self.buffer)");
    }
    Indent(-1);

    // Generate imported senders accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("# Imported senders");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("@property");
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_sender(self):");
            Indent(1);
            WriteLineIndent("return self._" + CppCommon::StringUtils::ToLower(*import) + "_sender");
            Indent(-1);
        }
    }

    // Generate sender models accessors
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("# Sender models accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("@property");
                WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*s->name) + "_model(self):");
                Indent(1);
                WriteLineIndent("return self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model");
                Indent(-1);
            }
        }
    }

    // Generate sender methods
    WriteLine();
    WriteLineIndent("# Send methods");
    WriteLine();
    WriteLineIndent("def send(self, value):");
    Indent(1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("if isinstance(value, " + *s->name + ") and (value.fbe_type == self." + CppCommon::StringUtils::ToLower(*s->name) + "_model.fbe_type):");
                Indent(1);
                WriteLineIndent("return self.send_" + CppCommon::StringUtils::ToLower(*s->name) + "(value)");
                Indent(-1);
            }
        }
    }
    if (p->import)
    {
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = self._" + CppCommon::StringUtils::ToLower(*import) + "_sender.send(value)");
            WriteLineIndent("if result > 0:");
            Indent(1);
            WriteLineIndent("return result");
            Indent(-1);
        }
    }
    WriteLineIndent("return 0");
    Indent(-1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("def send_" + CppCommon::StringUtils::ToLower(*s->name) + "(self, value):");
                Indent(1);
                WriteLineIndent("# Serialize the value into the FBE stream");
                WriteLineIndent("serialized = self." + CppCommon::StringUtils::ToLower(*s->name) + "_model.serialize(value)");
                WriteLineIndent("assert (serialized > 0), \"" + *p->name + "." + *s->name + " serialization failed!\"");
                WriteLineIndent("assert self." + CppCommon::StringUtils::ToLower(*s->name) + "_model.verify(), \"" + *p->name + "." + *s->name + " validation failed!\"");
                WriteLine();
                WriteLineIndent("# Log the value");
                WriteLineIndent("if self.logging:");
                Indent(1);
                WriteLineIndent("message = str(value)");
                WriteLineIndent("self.on_send_log(message)");
                Indent(-1);
                WriteLine();
                WriteLineIndent("# Send the serialized value");
                WriteLineIndent("return self.send_serialized(serialized)");
                Indent(-1);
            }
        }
    }

    // Generate sender message handler
    WriteLine();
    WriteLineIndent("# Send message handler");
    WriteLineIndent("def on_send(self, buffer, offset, size):");
    Indent(1);
    WriteLineIndent("raise NotImplementedError(\"" + *p->name + ".Sender.on_send() not implemented!\")");
    Indent(-1);

    // Generate sender end
    Indent(-1);
}

void GeneratorPython::GenerateReceiver(const std::shared_ptr<Package>& p, bool final)
{
    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate receiver begin
    WriteLine();
    WriteLine();
    if (final)
        WriteLineIndent("# Fast Binary Encoding " + *p->name + " final receiver");
    else
        WriteLineIndent("# Fast Binary Encoding " + *p->name + " receiver");
    WriteLineIndent("class " + receiver + "(fbe.Receiver):");
    Indent(1);

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate receiver __slots__
    if (p->import || messages)
    {
        WriteIndent("__slots__ = ");
        if (p->import)
        {
            for (const auto& import : p->import->imports)
                Write("\"_" + CppCommon::StringUtils::ToLower(*import) + "_receiver\", ");
        }
        if (p->body)
        {
            for (const auto& s : p->body->structs)
            {
                if (s->message)
                {
                    Write("\"_" + CppCommon::StringUtils::ToLower(*s->name) + "_value\", ");
                    Write("\"_" + CppCommon::StringUtils::ToLower(*s->name) + "_model\", ");
                }
            }
        }
        WriteLine();
        WriteLine();
    }

    // Generate receiver constructor
    WriteLineIndent("def __init__(self, buffer=None):");
    Indent(1);
    WriteLineIndent("super().__init__(buffer, " + std::string(final ? "True" : "False") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*import) + "_receiver = " + *import + "." + receiver + "(self.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*s->name) + "_value = " + *s->name + "()");
                WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model = " + *s->name + model + "()");
            }
        }
    }
    Indent(-1);

    // Generate imported receiver accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("# Imported receivers");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("@property");
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_receiver(self):");
            Indent(1);
            WriteLineIndent("return self._" + CppCommon::StringUtils::ToLower(*import) + "_receiver");
            Indent(-1);
            WriteLine();
            WriteLineIndent("@" + CppCommon::StringUtils::ToLower(*import) + "_receiver.setter");
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_receiver(self, receiver):");
            Indent(1);
            WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*import) + "_receiver = receiver");
            Indent(-1);
        }
    }

    // Generate receiver handlers
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("# Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("def on_receive_" + CppCommon::StringUtils::ToLower(*s->name) + "(self, value):");
                Indent(1);
                WriteLineIndent("pass");
                Indent(-1);
            }
        }
        WriteLine();
    }

    // Generate receiver message handler
    WriteLineIndent("def on_receive(self, type, buffer, offset, size):");
    Indent(1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("if type == " + *s->name + model + ".TYPE:");
                Indent(1);
                WriteLineIndent("# Deserialize the value from the FBE stream");
                WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model.attach_buffer(buffer, offset)");
                WriteLineIndent("assert self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model.verify(), \"" + *p->name + "." + *s->name + " validation failed!\"");
                WriteLineIndent("(_, deserialized) = self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model.deserialize(self._" + CppCommon::StringUtils::ToLower(*s->name) + "_value)");
                WriteLineIndent("assert (deserialized > 0), \"" + *p->name + "." + *s->name + " deserialization failed!\"");
                WriteLine();
                WriteLineIndent("# Log the value");
                WriteLineIndent("if self.logging:");
                Indent(1);
                WriteLineIndent("message = str(self._" + CppCommon::StringUtils::ToLower(*s->name) + "_value)");
                WriteLineIndent("self.on_receive_log(message)");
                Indent(-1);
                WriteLine();
                WriteLineIndent("# Call receive handler with deserialized value");
                WriteLineIndent("self.on_receive_" + CppCommon::StringUtils::ToLower(*s->name) + "(self._" + CppCommon::StringUtils::ToLower(*s->name) + "_value)");
                WriteLineIndent("return True");
                Indent(-1);
            }
        }
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (self." + CppCommon::StringUtils::ToLower(*import) + "_receiver is not None) and self." + CppCommon::StringUtils::ToLower(*import) + "_receiver.on_receive(type, buffer, offset, size):");
            Indent(1);
            WriteLineIndent("return True");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return False");
    Indent(-1);

    // Generate receiver end
    Indent(-1);
}

void GeneratorPython::GenerateProxy(const std::shared_ptr<Package>& p, bool final)
{
    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate proxy begin
    WriteLine();
    WriteLine();
    if (final)
        WriteLineIndent("# Fast Binary Encoding " + *p->name + " final proxy");
    else
        WriteLineIndent("# Fast Binary Encoding " + *p->name + " proxy");
    WriteLineIndent("class " + proxy + "(fbe.Receiver):");
    Indent(1);

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate proxy __slots__
    if (p->import || messages)
    {
        WriteIndent("__slots__ = ");
        if (p->import)
        {
            for (const auto& import : p->import->imports)
                Write("\"_" + CppCommon::StringUtils::ToLower(*import) + "_proxy\", ");
        }
        if (p->body)
        {
            for (const auto& s : p->body->structs)
                if (s->message)
                    Write("\"_" + CppCommon::StringUtils::ToLower(*s->name) + "_model\", ");
        }
        WriteLine();
        WriteLine();
    }

    // Generate proxy constructor
    WriteLineIndent("def __init__(self, buffer=None):");
    Indent(1);
    WriteLineIndent("super().__init__(buffer, " + std::string(final ? "True" : "False") + ")");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*import) + "_proxy = " + *import + "." + proxy + "(self.buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model = " + *s->name + model + "()");
    }
    Indent(-1);

    // Generate imported proxy accessors
    if (p->import)
    {
        WriteLine();
        WriteLineIndent("# Imported proxy");
        for (const auto& import : p->import->imports)
        {
            WriteLine();
            WriteLineIndent("@property");
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_proxy(self):");
            Indent(1);
            WriteLineIndent("return self._" + CppCommon::StringUtils::ToLower(*import) + "_proxy");
            Indent(-1);
            WriteLine();
            WriteLineIndent("@" + CppCommon::StringUtils::ToLower(*import) + "_proxy.setter");
            WriteLineIndent("def " + CppCommon::StringUtils::ToLower(*import) + "_proxy(self, proxy):");
            Indent(1);
            WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*import) + "_proxy = proxy");
            Indent(-1);
        }
    }

    // Generate proxy handlers
    if (p->body)
    {
        WriteLine();
        WriteLineIndent("# Receive handlers");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("def on_proxy_" + CppCommon::StringUtils::ToLower(*s->name) + "(self, model, type, buffer, offset, size):");
                Indent(1);
                WriteLineIndent("pass");
                Indent(-1);
            }
        }
        WriteLine();
    }

    // Generate proxy message handler
    WriteLineIndent("def on_receive(self, type, buffer, offset, size):");
    Indent(1);
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLine();
                WriteLineIndent("if type == " + *s->name + model + ".TYPE:");
                Indent(1);
                WriteLineIndent("# Attach the FBE stream to the proxy model");
                WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model.attach_buffer(buffer, offset)");
                WriteLineIndent("assert self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model.verify(), \"" + *p->name + "." + *s->name + " validation failed!\"");
                WriteLine();
                WriteLineIndent("fbe_begin = self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model.model.get_begin()");
                WriteLineIndent("if fbe_begin == 0:");
                Indent(1);
                WriteLineIndent("return False");
                Indent(-1);
                WriteLineIndent("# Call proxy handler");
                WriteLineIndent("self.on_proxy_" + CppCommon::StringUtils::ToLower(*s->name) + "(self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model, type, buffer, offset, size)");
                WriteLineIndent("self._" + CppCommon::StringUtils::ToLower(*s->name) + "_model.model.get_end(fbe_begin)");
                WriteLineIndent("return True");
                Indent(-1);
            }
        }
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (self." + CppCommon::StringUtils::ToLower(*import) + "_proxy is not None) and self." + CppCommon::StringUtils::ToLower(*import) + "_proxy.on_receive(type, buffer, offset, size):");
            Indent(1);
            WriteLineIndent("return True");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return False");
    Indent(-1);

    // Generate proxy end
    Indent(-1);
}

bool GeneratorPython::IsPrimitiveType(const std::string& type)
{
    return ((type == "bool") || (type == "byte") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double"));
}

bool GeneratorPython::IsPythonType(const std::string& type)
{
    return IsPrimitiveType(type) || (type == "bytes") || (type == "decimal") || (type == "string") || (type == "timestamp") || (type == "uuid");
}

std::string GeneratorPython::ConvertEnumSize(const std::string& type)
{
    if (type == "byte")
        return "1";
    else if (type == "char")
        return "1";
    else if (type == "wchar")
        return "4";
    else if (type == "int8")
        return "1";
    else if (type == "uint8")
        return "1";
    else if (type == "int16")
        return "2";
    else if (type == "uint16")
        return "2";
    else if (type == "int32")
        return "4";
    else if (type == "uint32")
        return "4";
    else if (type == "int64")
        return "8";
    else if (type == "uint64")
        return "8";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorPython::ConvertEnumType(const std::string& type)
{
    if (type == "byte")
        return "byte";
    else if (type == "char")
        return "uint8";
    else if (type == "wchar")
        return "uint32";
    else if (type == "int8")
        return "int8";
    else if (type == "uint8")
        return "uint8";
    else if (type == "int16")
        return "int16";
    else if (type == "uint16")
        return "uint16";
    else if (type == "int32")
        return "int32";
    else if (type == "uint32")
        return "uint32";
    else if (type == "int64")
        return "int64";
    else if (type == "uint64")
        return "uint64";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorPython::ConvertEnumConstant(const std::string& type, const std::string& value)
{
    if (((type == "char") || (type == "wchar")) && CppCommon::StringUtils::StartsWith(value, "'"))
        return "ord(" + value + ")";
    else if (type.empty())
        return value;

    return "int(" + value + ")";
}

std::string GeneratorPython::ConvertTypeName(const std::string& type, bool optional)
{
    if (type == "bool")
        return "bool";
    else if ((type == "byte") || (type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64") || (type == "timestamp"))
        return "int";
    else if (type == "bytes")
        return "bytearray";
    else if ((type == "float") || (type == "double"))
        return "float";
    else if (type == "decimal")
        return "decimal.Decimal";
    else if (type == "string")
        return "str";
    else if (type == "uuid")
        return "uuid.UUID";

    return type;
}

std::string GeneratorPython::ConvertTypeName(const StructField& field)
{
    if (field.array)
        return "list";
    else if (field.vector)
        return "list";
    else if (field.list)
        return "list";
    else if (field.set)
        return "set";
    else if (field.map)
        return "dict";
    else if (field.hash)
        return "dict";

    return ConvertTypeName(*field.type, field.optional);
}

std::string GeneratorPython::ConvertTypeFieldName(const std::string& type, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (type == "bool")
        return "fbe." + modelType + "ModelBool";
    else if (type == "byte")
        return "fbe." + modelType + "ModelByte";
    else if (type == "bytes")
        return "fbe." + modelType + "ModelBytes";
    else if (type == "char")
        return "fbe." + modelType + "ModelChar";
    else if (type == "wchar")
        return "fbe." + modelType + "ModelWChar";
    else if (type == "int8")
        return "fbe." + modelType + "ModelInt8";
    else if (type == "uint8")
        return "fbe." + modelType + "ModelUInt8";
    else if (type == "int16")
        return "fbe." + modelType + "ModelInt16";
    else if (type == "uint16")
        return "fbe." + modelType + "ModelUInt16";
    else if (type == "int32")
        return "fbe." + modelType + "ModelInt32";
    else if (type == "uint32")
        return "fbe." + modelType + "ModelUInt32";
    else if (type == "int64")
        return "fbe." + modelType + "ModelInt64";
    else if (type == "uint64")
        return "fbe." + modelType + "ModelUInt64";
    else if (type == "float")
        return "fbe." + modelType + "ModelFloat";
    else if (type == "double")
        return "fbe." + modelType + "ModelDouble";
    else if (type == "decimal")
        return "fbe." + modelType + "ModelDecimal";
    else if (type == "timestamp")
        return "fbe." + modelType + "ModelTimestamp";
    else if (type == "string")
        return "fbe." + modelType + "ModelString";
    else if (type == "uuid")
        return "fbe." + modelType + "ModelUUID";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ns + modelType + "Model" + ConvertTypeName(t, false);
}

std::string GeneratorPython::ConvertTypeFieldInitialization(const std::string& type, bool optional, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (optional)
        return "fbe." + modelType + "ModelOptional(" + ConvertTypeFieldInitialization(type, false, offset, final)+ ", buffer, " + offset + ")";
    else
        return ConvertTypeFieldName(type, final) + "(buffer, " + offset + ")";
}

std::string GeneratorPython::ConvertTypeFieldInitialization(const StructField& field, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return "fbe." + modelType + "ModelArray(" + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ", " + std::to_string(field.N) + ")";
    else if (field.vector || field.list)
        return "fbe." + modelType + "ModelVector(" + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ")";
    else if (field.set)
        return "fbe." + modelType + "ModelSet(" + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ")";
    else if (field.map || field.hash)
        return "fbe." + modelType + "ModelMap(" + ConvertTypeFieldInitialization(*field.key, false, offset, final) + ", " + ConvertTypeFieldInitialization(*field.type, field.optional, offset, final) + ", buffer, " + offset + ")";
    else
        return ConvertTypeFieldInitialization(*field.type, field.optional, offset, final);
}

std::string GeneratorPython::ConvertConstant(const std::string& type, const std::string& value, bool optional)
{
    if (value == "true")
        return "True";
    else if (value == "false")
        return "False";
    else if (value == "null")
        return "None";
    else if (value == "min")
    {
        if ((type == "byte") || (type == "uint8") || (type == "uint16") || (type == "uint32") || (type == "uint64"))
            return "0";
        else if (type == "int8")
            return "-128";
        else if (type == "int16")
            return "-32768";
        else if (type == "int32")
            return "-2147483648";
        else if (type == "int64")
            return "-9223372036854775808";

        yyerror("Unsupported type " + type + " for 'min' constant");
        return "";
    }
    else if (value == "max")
    {
        if (type == "byte")
            return "255";
        else if (type == "int8")
            return "127";
        else if (type == "uint8")
            return "255";
        else if (type == "int16")
            return "32767";
        else if (type == "uint16")
            return "65535";
        else if (type == "int32")
            return "2147483647";
        else if (type == "uint32")
            return "4294967295";
        else if (type == "int64")
            return "9223372036854775807";
        else if (type == "uint64")
            return "18446744073709551615";

        yyerror("Unsupported type " + type + " for 'max' constant");
        return "";
    }
    else if (value == "epoch")
        return "fbe.epoch()";
    else if (value == "utc")
        return "fbe.utc()";
    else if (value == "uuid0")
        return "uuid.UUID(int=0)";
    else if (value == "uuid1")
        return "uuid.uuid1()";
    else if (value == "uuid4")
        return "uuid.uuid4()";

    if (type == "bool")
        return "bool(" + value + ")";
    else if (((type == "char") || (type == "wchar")) && !CppCommon::StringUtils::StartsWith(value, "'"))
        return "chr(" + value + ")";
    else if ((type == "byte") || (type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64") || (type == "timestamp"))
        return "int(" + value + ")";
    else if ((type == "float") || (type == "double"))
        return "float(" + value + ")";
    else if (type == "decimal")
        return "decimal.Decimal(\"" + value + "\")";
    else if (type == "uuid")
        return "uuid.UUID(" + value + ")";

    return value;
}

std::string GeneratorPython::ConvertDefaultOrNone(const std::string& type, bool optional)
{
    if (optional)
        return "None";

    if (type == "bool")
        return "False";
    else if (type == "byte")
        return "0";
    else if (type == "bytes")
        return "None";
    else if ((type == "char") || (type == "wchar"))
        return "'\\0'";
    else if ((type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64"))
        return "0";
    else if ((type == "float") || (type == "double"))
        return "0.0";
    else if (type == "decimal")
        return "decimal.Decimal(0)";
    else if (type == "timestamp")
        return "fbe.epoch()";
    else if (type == "string")
        return "\"\"";
    else if (type == "uuid")
        return "uuid.UUID(int=0)";

    return "None";
}

std::string GeneratorPython::ConvertDefault(const std::string& type, bool optional)
{
    if (optional)
        return "None";

    if (type == "bool")
        return "False";
    else if (type == "byte")
        return "0";
    else if (type == "bytes")
        return "bytearray()";
    else if ((type == "char") || (type == "wchar"))
        return "'\\0'";
    else if ((type == "int8") || (type == "uint8") || (type == "int16") || (type == "uint16") || (type == "int32") || (type == "uint32") || (type == "int64") || (type == "uint64"))
        return "0";
    else if ((type == "float") || (type == "double"))
        return "0.0";
    else if (type == "decimal")
        return "decimal.Decimal(0)";
    else if (type == "timestamp")
        return "fbe.epoch()";
    else if (type == "string")
        return "\"\"";
    else if (type == "uuid")
        return "uuid.UUID(int=0)";

    return type + "()";
}

std::string GeneratorPython::ConvertDefault(const StructField& field)
{
    if (field.value)
        return ConvertConstant(*field.type, *field.value, field.optional);

    if (field.array)
        return "[" + ConvertDefault(*field.type, field.optional) + "]*" + std::to_string(field.N);
    else if (field.vector || field.list || field.set || field.map || field.hash)
        return ConvertTypeName(field) + "()";

    return ConvertDefault(*field.type, field.optional);
}

void GeneratorPython::WriteOutputStreamType(const std::string& type, const std::string& name, bool optional)
{
    if (type == "bool")
        WriteLineIndent("sb.append(\"true\" if " + name + " else \"false\")");
    else if (type == "bytes")
        WriteLineIndent("sb.append(\"bytes[\" + str(len(" + name + ")) + \"]\")");
    else if ((type == "char") || (type == "wchar"))
        WriteLineIndent("sb.append(\"'\" + str(" + name + ") + \"'\")");
    else if ((type == "string") || (type == "uuid"))
        WriteLineIndent("sb.append(\"\\\"\" + str(" + name + ") + \"\\\"\")");
    else
        WriteLineIndent("sb.append(str(" + name + "))");
}

void GeneratorPython::WriteOutputStreamValue(const std::string& type, const std::string& name, bool optional, bool separate)
{
    if (optional || (type == "bytes") || (type == "decimal") || (type == "string") || (type == "timestamp") || (type == "uuid"))
    {
        WriteLineIndent("if " + name + " is not None:");
        Indent(1);
        if (separate)
            WriteLineIndent("sb.append(\"\" if first else \",\")");
        WriteOutputStreamType(type, name, true);
        Indent(-1);
        WriteLineIndent("else:");
        Indent(1);
        if (separate)
            WriteLineIndent("sb.append(\"\" if first else \",\")");
        WriteLineIndent("sb.append(\"null\")");
        Indent(-1);
    }
    else
    {
        if (separate)
            WriteLineIndent("sb.append(\"\" if first else \",\")");
        WriteOutputStreamType(type, name, false);
    }
}

} // namespace FBE
